Add project files.

This commit is contained in:
Shmew
2020-06-21 13:21:54 -05:00
commit 70e3c82c95
134 changed files with 23686 additions and 0 deletions

14
.editorconfig Normal file
View File

@@ -0,0 +1,14 @@
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
# All files
[*]
indent_style = space
# Xml files
[*.xml]
indent_size = 2
# YAML files
[*.yml]
indent_size = 2
indent_style = space

26
.gitattributes vendored Normal file
View File

@@ -0,0 +1,26 @@
# Auto detect text files
* text=auto
# Custom for Visual Studio
*.cs diff=csharp text=auto eol=lf
*.vb diff=csharp text=auto eol=lf
*.fs diff=csharp text=auto eol=lf
*.fsi diff=csharp text=auto eol=lf
*.fsx diff=csharp text=auto eol=lf
*.sln text eol=crlf merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Propose a new feature
title: ''
labels: ''
assignees: ''
---
## Is your feature request related to a problem?
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
## Describe the solution you'd like
<!-- A clear and concise description of what you want to happen. -->
## Describe alternatives you've considered
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
## Additional context
<!-- Add any other context or screenshots about the feature request here. -->

27
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,27 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
## Description
<!-- A clear and concise description of what the bug is. -->
## Steps to reproduce
<!-- Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error -->
## Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
## Screenshots
<!-- If applicable, add screenshots to help explain your problem. -->
## Additional context
<!-- Add any other context about the problem here. -->

38
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,38 @@
<!-- Please refer to our contributing documentation for any questions on submitting a pull request. -->
## Pull request checklist
Please check if your PR fulfills the following requirements:
- [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been reviewed and added / updated if needed (for bug fixes / features)
- [ ] Build (`fake build` or `.\build.cmd`) on local branch was successful
## Pull request type
<!-- Please try to limit your pull request to one type, submit multiple pull requests if needed. -->
Please check the type of change your PR introduces:
- [ ] Bugfix
- [ ] Feature
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] Documentation content changes
- [ ] Other (please describe):
## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by this PR. -->
-
-
-
## Does this introduce a breaking change?
<!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. -->
- [ ] Yes
- [ ] No
## Other information
<!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->

12
.github/workflows/secrets.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: Secrets Checker
on: push
jobs:
seekret:
runs-on: ubuntu-latest
steps:
- name: 'Check for secrets'
uses: 'docker://cdssnc/seekret-github-action'

19
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Stale issue message'
stale-pr-message: 'Stale pull request message'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'

380
.gitignore vendored Normal file
View File

@@ -0,0 +1,380 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.rsuser
*.user
*.sln.docstates
*.userosscache
# Xamarin Studio / monodevelop user-specific
*.userprefs
*.dll.mdb
*.exe.mdb
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
build/
[Dd]ist/
.fable/
[Bb]in/
[Oo]bj/
[Pp]ublic/*bundle.js*
[Pp]ublic/*.md
# Exclude doc generation and logs
docsrc/content/license.md
docsrc/content/release-notes.md
docsrc/tools/FSharp.Formatting.svclog
# FAKE build cache
.fake/
# Test results produced by build
*Results.xml
*.VisualState.xml
# Chutzpah Test files
_Chutzpah*
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.svclog
*.vspscc
*.vssscc
.builds
*.pidb
*.log
*.scc
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# Other Visual Studio data
.vs/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Enable nuget.exe in the .nuget folder (though normally executables are not tracked)
!.nuget/NuGet.exe
*.nupkg
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Nuget outputs
nuget/*.nupkg
release.cmd
release.sh
localpackages/
*.orig
# Paket dependency manager
paket-files/
paket.local
.paket/load
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# Windows Azure Build Output
csx
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directory
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# VisualStudioCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# Others
sql/
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
.ionide
[Kk]ey*.json
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
#LightSwitch generated files
GeneratedArtifacts/
_Pvt_Extensions/
ModelManifest.xml
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Headless browser
.local-chromium
# =========================
# Windows detritus
# =========================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Mac desktop service store files
.DS_Store

View File

@@ -0,0 +1,488 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Prevent dotnet template engine to parse this file -->
<!--/-:cnd:noEmit-->
<PropertyGroup>
<!-- make MSBuild track this file for incremental builds. -->
<!-- ref https://blogs.msdn.microsoft.com/msbuild/2005/09/26/how-to-ensure-changes-to-a-custom-target-file-prompt-a-rebuild/ -->
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<DetectedMSBuildVersion>$(MSBuildVersion)</DetectedMSBuildVersion>
<DetectedMSBuildVersion Condition="'$(MSBuildVersion)' == ''">15.0.0</DetectedMSBuildVersion>
<MSBuildSupportsHashing>false</MSBuildSupportsHashing>
<MSBuildSupportsHashing Condition=" '$(DetectedMSBuildVersion)' &gt; '15.8.0' ">true</MSBuildSupportsHashing>
<!-- Mark that this target file has been loaded. -->
<IsPaketRestoreTargetsFileLoaded>true</IsPaketRestoreTargetsFileLoaded>
<PaketToolsPath>$(MSBuildThisFileDirectory)</PaketToolsPath>
<PaketRootPath>$(MSBuildThisFileDirectory)..\</PaketRootPath>
<PaketRestoreCacheFile>$(PaketRootPath)paket-files\paket.restore.cached</PaketRestoreCacheFile>
<PaketLockFilePath>$(PaketRootPath)paket.lock</PaketLockFilePath>
<PaketBootstrapperStyle>classic</PaketBootstrapperStyle>
<PaketBootstrapperStyle Condition="Exists('$(PaketToolsPath)paket.bootstrapper.proj')">proj</PaketBootstrapperStyle>
<PaketExeImage>assembly</PaketExeImage>
<PaketExeImage Condition=" '$(PaketBootstrapperStyle)' == 'proj' ">native</PaketExeImage>
<MonoPath Condition="'$(MonoPath)' == '' AND Exists('/Library/Frameworks/Mono.framework/Commands/mono')">/Library/Frameworks/Mono.framework/Commands/mono</MonoPath>
<MonoPath Condition="'$(MonoPath)' == ''">mono</MonoPath>
<!-- PaketBootStrapper -->
<PaketBootStrapperExePath Condition=" '$(PaketBootStrapperExePath)' == '' AND Exists('$(PaketRootPath)paket.bootstrapper.exe')">$(PaketRootPath)paket.bootstrapper.exe</PaketBootStrapperExePath>
<PaketBootStrapperExePath Condition=" '$(PaketBootStrapperExePath)' == '' ">$(PaketToolsPath)paket.bootstrapper.exe</PaketBootStrapperExePath>
<PaketBootStrapperExeDir Condition=" Exists('$(PaketBootStrapperExePath)') " >$([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\</PaketBootStrapperExeDir>
<PaketBootStrapperCommand Condition=" '$(OS)' == 'Windows_NT' ">"$(PaketBootStrapperExePath)"</PaketBootStrapperCommand>
<PaketBootStrapperCommand Condition=" '$(OS)' != 'Windows_NT' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)"</PaketBootStrapperCommand>
<!-- Disable Paket restore under NCrunch build -->
<PaketRestoreDisabled Condition="'$(NCrunch)' == '1'">True</PaketRestoreDisabled>
<!-- Disable test for CLI tool completely - overrideable via properties in projects or via environment variables -->
<PaketDisableCliTest Condition=" '$(PaketDisableCliTest)' == '' ">False</PaketDisableCliTest>
<PaketIntermediateOutputPath Condition=" '$(PaketIntermediateOutputPath)' == '' ">$(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/'))</PaketIntermediateOutputPath>
</PropertyGroup>
<!-- Resolve how paket should be called -->
<!-- Current priority is: local (1: repo root, 2: .paket folder) => 3: as CLI tool => as bootstrapper (4: proj Bootstrapper style, 5: BootstrapperExeDir) => 6: global path variable -->
<Target Name="SetPaketCommand" >
<!-- Test if paket is available in the standard locations. If so, that takes priority. Case 1/2 - non-windows specific -->
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT' ">
<!-- no windows, try native paket as default, root => tool -->
<PaketExePath Condition=" '$(PaketExePath)' == '' AND Exists('$(PaketRootPath)paket') ">$(PaketRootPath)paket</PaketExePath>
<PaketExePath Condition=" '$(PaketExePath)' == '' AND Exists('$(PaketToolsPath)paket') ">$(PaketToolsPath)paket</PaketExePath>
</PropertyGroup>
<!-- Test if paket is available in the standard locations. If so, that takes priority. Case 2/2 - same across platforms -->
<PropertyGroup>
<!-- root => tool -->
<PaketExePath Condition=" '$(PaketExePath)' == '' AND Exists('$(PaketRootPath)paket.exe') ">$(PaketRootPath)paket.exe</PaketExePath>
<PaketExePath Condition=" '$(PaketExePath)' == '' AND Exists('$(PaketToolsPath)paket.exe') ">$(PaketToolsPath)paket.exe</PaketExePath>
</PropertyGroup>
<!-- If paket hasn't be found in standard locations, test for CLI tool usage. -->
<!-- First test: Is CLI configured to be used in "dotnet-tools.json"? - can result in a false negative; only a positive outcome is reliable. -->
<PropertyGroup Condition=" '$(PaketExePath)' == '' ">
<_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json"))</_DotnetToolsJson>
<_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"'))</_ConfigContainsPaket>
<_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false</_ConfigContainsPaket>
</PropertyGroup>
<!-- Second test: Call 'dotnet paket' and see if it returns without an error. Mute all the output. Only run if previous test failed and the test has not been disabled. -->
<!-- WARNING: This method can lead to processes hanging forever, and should be used as little as possible. See https://github.com/fsprojects/Paket/issues/3705 for details. -->
<Exec Condition=" '$(PaketExePath)' == '' AND !$(PaketDisableCliTest) AND !$(_ConfigContainsPaket)" Command="dotnet paket --version" IgnoreExitCode="true" StandardOutputImportance="low" StandardErrorImportance="low" >
<Output TaskParameter="ExitCode" PropertyName="LocalPaketToolExitCode" />
</Exec>
<!-- If paket is installed as CLI use that. Again, only if paket haven't already been found in standard locations. -->
<PropertyGroup Condition=" '$(PaketExePath)' == '' AND ($(_ConfigContainsPaket) OR '$(LocalPaketToolExitCode)' == '0') ">
<_PaketCommand>dotnet paket</_PaketCommand>
</PropertyGroup>
<!-- If neither local files nor CLI tool can be found, final attempt is searching for boostrapper config before falling back to global path variable. -->
<PropertyGroup Condition=" '$(PaketExePath)' == '' AND '$(_PaketCommand)' == '' ">
<!-- Test for bootstrapper setup -->
<PaketExePath Condition=" '$(PaketExePath)' == '' AND '$(PaketBootstrapperStyle)' == 'proj' ">$(PaketToolsPath)paket</PaketExePath>
<PaketExePath Condition=" '$(PaketExePath)' == '' AND Exists('$(PaketBootStrapperExeDir)') ">$(PaketBootStrapperExeDir)paket</PaketExePath>
<!-- If all else fails, use global path approach. -->
<PaketExePath Condition=" '$(PaketExePath)' == ''">paket</PaketExePath>
</PropertyGroup>
<!-- If not using CLI, setup correct execution command. -->
<PropertyGroup Condition=" '$(_PaketCommand)' == '' ">
<_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)"))</_PaketExeExtension>
<_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)"</_PaketCommand>
<_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"</_PaketCommand>
<_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)"</_PaketCommand>
</PropertyGroup>
<!-- The way to get a property to be available outside the target is to use this task. -->
<CreateProperty Value="$(_PaketCommand)">
<Output TaskParameter="Value" PropertyName="PaketCommand"/>
</CreateProperty>
</Target>
<Target Name="PaketBootstrapping" Condition="Exists('$(PaketToolsPath)paket.bootstrapper.proj')">
<MSBuild Projects="$(PaketToolsPath)paket.bootstrapper.proj" Targets="Restore" />
</Target>
<!-- Official workaround for https://docs.microsoft.com/en-us/visualstudio/msbuild/getfilehash-task?view=vs-2019 -->
<UsingTask TaskName="Microsoft.Build.Tasks.GetFileHash" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Condition=" '$(MSBuildSupportsHashing)' == 'true' And '$(DetectedMSBuildVersion)' &lt; '16.0.360' " />
<UsingTask TaskName="Microsoft.Build.Tasks.VerifyFileHash" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Condition=" '$(MSBuildSupportsHashing)' == 'true' And '$(DetectedMSBuildVersion)' &lt; '16.0.360' " />
<Target Name="PaketRestore" Condition="'$(PaketRestoreDisabled)' != 'True'" BeforeTargets="_GenerateDotnetCliToolReferenceSpecs;_GenerateProjectRestoreGraphPerFramework;_GenerateRestoreGraphWalkPerFramework;CollectPackageReferences" DependsOnTargets="SetPaketCommand;PaketBootstrapping">
<!-- Step 1 Check if lockfile is properly restored (if the hash of the lockfile and the cache-file match) -->
<PropertyGroup>
<PaketRestoreRequired>true</PaketRestoreRequired>
<NoWarn>$(NoWarn);NU1603;NU1604;NU1605;NU1608</NoWarn>
<CacheFilesExist>false</CacheFilesExist>
<CacheFilesExist Condition=" Exists('$(PaketRestoreCacheFile)') And Exists('$(PaketLockFilePath)') ">true</CacheFilesExist>
</PropertyGroup>
<!-- Read the hash of the lockfile -->
<GetFileHash Condition=" '$(MSBuildSupportsHashing)' == 'true' And '$(CacheFilesExist)' == 'true' " Files="$(PaketLockFilePath)" Algorithm="SHA256" HashEncoding="hex" >
<Output TaskParameter="Hash" PropertyName="PaketRestoreLockFileHash" />
</GetFileHash>
<!-- Read the hash of the cache, which is json, but a very simple key value object -->
<PropertyGroup Condition=" '$(MSBuildSupportsHashing)' == 'true' And '$(CacheFilesExist)' == 'true' ">
<PaketRestoreCachedContents>$([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)'))</PaketRestoreCachedContents>
</PropertyGroup>
<ItemGroup Condition=" '$(MSBuildSupportsHashing)' == 'true' And '$(CacheFilesExist)' == 'true' ">
<!-- Parse our simple 'paket.restore.cached' json ...-->
<PaketRestoreCachedSplitObject Include="$([System.Text.RegularExpressions.Regex]::Split(`$(PaketRestoreCachedContents)`, `{|}|,`))"></PaketRestoreCachedSplitObject>
<!-- Keep Key, Value ItemGroup-->
<PaketRestoreCachedKeyValue Include="@(PaketRestoreCachedSplitObject)"
Condition=" $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `&quot;: &quot;`).Length) &gt; 1 ">
<Key>$([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``))</Key>
<Value>$([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``))</Value>
</PaketRestoreCachedKeyValue>
</ItemGroup>
<PropertyGroup Condition=" '$(MSBuildSupportsHashing)' == 'true' And '$(CacheFilesExist)' == 'true' ">
<!-- Retrieve the hashes we are interested in -->
<PackagesDownloadedHash Condition=" '%(PaketRestoreCachedKeyValue.Key)' == 'packagesDownloadedHash' ">%(PaketRestoreCachedKeyValue.Value)</PackagesDownloadedHash>
<ProjectsRestoredHash Condition=" '%(PaketRestoreCachedKeyValue.Key)' == 'projectsRestoredHash' ">%(PaketRestoreCachedKeyValue.Value)</ProjectsRestoredHash>
</PropertyGroup>
<PropertyGroup Condition=" '$(MSBuildSupportsHashing)' == 'true' And '$(CacheFilesExist)' == 'true' ">
<!-- If the restore file doesn't exist we need to restore, otherwise only if hashes don't match -->
<PaketRestoreRequired>true</PaketRestoreRequired>
<PaketRestoreRequired Condition=" '$(PaketRestoreLockFileHash)' == '$(ProjectsRestoredHash)' ">false</PaketRestoreRequired>
<PaketRestoreRequired Condition=" '$(PaketRestoreLockFileHash)' == '' ">true</PaketRestoreRequired>
</PropertyGroup>
<!--
This value should match the version in the props generated by paket
If they differ, this means we need to do a restore in order to ensure correct dependencies
-->
<PropertyGroup Condition="'$(PaketPropsVersion)' != '5.185.3' ">
<PaketRestoreRequired>true</PaketRestoreRequired>
</PropertyGroup>
<!-- Do a global restore if required -->
<Warning Text="This version of MSBuild (we assume '$(DetectedMSBuildVersion)' or older) doesn't support GetFileHash, so paket fast restore is disabled." Condition=" '$(MSBuildSupportsHashing)' != 'true' " />
<Error Text="Stop build because of PAKET_ERROR_ON_MSBUILD_EXEC and we always call the bootstrapper" Condition=" '$(PAKET_ERROR_ON_MSBUILD_EXEC)' == 'true' AND '$(PaketBootstrapperStyle)' == 'classic' AND Exists('$(PaketBootStrapperExePath)') AND !(Exists('$(PaketExePath)'))" />
<Exec Command='$(PaketBootStrapperCommand)' Condition=" '$(PaketBootstrapperStyle)' == 'classic' AND Exists('$(PaketBootStrapperExePath)') AND !(Exists('$(PaketExePath)'))" ContinueOnError="false" />
<Error Text="Stop build because of PAKET_ERROR_ON_MSBUILD_EXEC and we need a full restore (hashes don't match)" Condition=" '$(PAKET_ERROR_ON_MSBUILD_EXEC)' == 'true' AND '$(PaketRestoreRequired)' == 'true' AND '$(PaketDisableGlobalRestore)' != 'true'" />
<Exec Command='$(PaketCommand) restore' Condition=" '$(PaketRestoreRequired)' == 'true' AND '$(PaketDisableGlobalRestore)' != 'true' " ContinueOnError="false" />
<!-- Step 2 Detect project specific changes -->
<ItemGroup>
<MyTargetFrameworks Condition="'$(TargetFramework)' != '' " Include="$(TargetFramework)"></MyTargetFrameworks>
<!-- Don't include all frameworks when msbuild explicitly asks for a single one -->
<MyTargetFrameworks Condition="'$(TargetFrameworks)' != '' AND '$(TargetFramework)' == '' " Include="$(TargetFrameworks)"></MyTargetFrameworks>
<PaketResolvedFilePaths Include="@(MyTargetFrameworks -> '$(PaketIntermediateOutputPath)\$(MSBuildProjectFile).%(Identity).paket.resolved')"></PaketResolvedFilePaths>
</ItemGroup>
<PropertyGroup>
<PaketReferencesCachedFilePath>$(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached</PaketReferencesCachedFilePath>
<!-- MyProject.fsproj.paket.references has the highest precedence -->
<PaketOriginalReferencesFilePath>$(MSBuildProjectFullPath).paket.references</PaketOriginalReferencesFilePath>
<!-- MyProject.paket.references -->
<PaketOriginalReferencesFilePath Condition=" !Exists('$(PaketOriginalReferencesFilePath)')">$(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references</PaketOriginalReferencesFilePath>
<!-- paket.references -->
<PaketOriginalReferencesFilePath Condition=" !Exists('$(PaketOriginalReferencesFilePath)')">$(MSBuildProjectDirectory)\paket.references</PaketOriginalReferencesFilePath>
<DoAllResolvedFilesExist>false</DoAllResolvedFilesExist>
<DoAllResolvedFilesExist Condition="Exists(%(PaketResolvedFilePaths.Identity))">true</DoAllResolvedFilesExist>
<PaketRestoreRequired>true</PaketRestoreRequired>
<PaketRestoreRequiredReason>references-file-or-cache-not-found</PaketRestoreRequiredReason>
</PropertyGroup>
<!-- Step 2 a Detect changes in references file -->
<PropertyGroup Condition="Exists('$(PaketOriginalReferencesFilePath)') AND Exists('$(PaketReferencesCachedFilePath)') ">
<PaketRestoreCachedHash>$([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)'))</PaketRestoreCachedHash>
<PaketRestoreReferencesFileHash>$([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)'))</PaketRestoreReferencesFileHash>
<PaketRestoreRequiredReason>references-file</PaketRestoreRequiredReason>
<PaketRestoreRequired Condition=" '$(PaketRestoreReferencesFileHash)' == '$(PaketRestoreCachedHash)' ">false</PaketRestoreRequired>
</PropertyGroup>
<PropertyGroup Condition="!Exists('$(PaketOriginalReferencesFilePath)') AND !Exists('$(PaketReferencesCachedFilePath)') ">
<!-- If both don't exist there is nothing to do. -->
<PaketRestoreRequired>false</PaketRestoreRequired>
</PropertyGroup>
<!-- Step 2 b detect relevant changes in project file (new targetframework) -->
<PropertyGroup Condition=" '$(DoAllResolvedFilesExist)' != 'true' ">
<PaketRestoreRequired>true</PaketRestoreRequired>
<PaketRestoreRequiredReason>target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths)</PaketRestoreRequiredReason>
</PropertyGroup>
<!-- Step 3 Restore project specific stuff if required -->
<Message Condition=" '$(PaketRestoreRequired)' == 'true' " Importance="low" Text="Detected a change ('$(PaketRestoreRequiredReason)') in the project file '$(MSBuildProjectFullPath)', calling paket restore" />
<Error Text="Stop build because of PAKET_ERROR_ON_MSBUILD_EXEC and we detected a change ('$(PaketRestoreRequiredReason)') in the project file '$(MSBuildProjectFullPath)'" Condition=" '$(PAKET_ERROR_ON_MSBUILD_EXEC)' == 'true' AND '$(PaketRestoreRequired)' == 'true' " />
<Exec Command='$(PaketCommand) restore --project "$(MSBuildProjectFullPath)" --output-path "$(PaketIntermediateOutputPath)" --target-framework "$(TargetFrameworks)"' Condition=" '$(PaketRestoreRequired)' == 'true' AND '$(TargetFramework)' == '' " ContinueOnError="false" />
<Exec Command='$(PaketCommand) restore --project "$(MSBuildProjectFullPath)" --output-path "$(PaketIntermediateOutputPath)" --target-framework "$(TargetFramework)"' Condition=" '$(PaketRestoreRequired)' == 'true' AND '$(TargetFramework)' != '' " ContinueOnError="false" />
<!-- This shouldn't actually happen, but just to be sure. -->
<PropertyGroup>
<DoAllResolvedFilesExist>false</DoAllResolvedFilesExist>
<DoAllResolvedFilesExist Condition="Exists(%(PaketResolvedFilePaths.Identity))">true</DoAllResolvedFilesExist>
</PropertyGroup>
<Error Condition=" '$(DoAllResolvedFilesExist)' != 'true' AND '$(ResolveNuGetPackages)' != 'False' " Text="One Paket file '@(PaketResolvedFilePaths)' is missing while restoring $(MSBuildProjectFile). Please delete 'paket-files/paket.restore.cached' and call 'paket restore'." />
<!-- Step 4 forward all msbuild properties (PackageReference, DotNetCliToolReference) to msbuild -->
<ReadLinesFromFile Condition="($(DesignTimeBuild) != true OR '$(PaketPropsLoaded)' != 'true') AND '@(PaketResolvedFilePaths)' != ''" File="%(PaketResolvedFilePaths.Identity)" >
<Output TaskParameter="Lines" ItemName="PaketReferencesFileLines"/>
</ReadLinesFromFile>
<ItemGroup Condition="($(DesignTimeBuild) != true OR '$(PaketPropsLoaded)' != 'true') AND '@(PaketReferencesFileLines)' != '' " >
<PaketReferencesFileLinesInfo Include="@(PaketReferencesFileLines)" >
<Splits>$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length)</Splits>
<PackageName>$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0])</PackageName>
<PackageVersion>$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1])</PackageVersion>
<AllPrivateAssets>$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4])</AllPrivateAssets>
<CopyLocal Condition="'%(PaketReferencesFileLinesInfo.Splits)' == '6'">$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5])</CopyLocal>
</PaketReferencesFileLinesInfo>
<PackageReference Include="%(PaketReferencesFileLinesInfo.PackageName)">
<Version>%(PaketReferencesFileLinesInfo.PackageVersion)</Version>
<PrivateAssets Condition=" ('%(PaketReferencesFileLinesInfo.AllPrivateAssets)' == 'true') Or ('$(PackAsTool)' == 'true') ">All</PrivateAssets>
<ExcludeAssets Condition=" '%(PaketReferencesFileLinesInfo.Splits)' == '6' And %(PaketReferencesFileLinesInfo.CopyLocal) == 'false'">runtime</ExcludeAssets>
<ExcludeAssets Condition=" '%(PaketReferencesFileLinesInfo.Splits)' != '6' And %(PaketReferencesFileLinesInfo.AllPrivateAssets) == 'exclude'">runtime</ExcludeAssets>
<Publish Condition=" '$(PackAsTool)' == 'true' ">true</Publish>
<AllowExplicitVersion>true</AllowExplicitVersion>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<PaketCliToolFilePath>$(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools</PaketCliToolFilePath>
</PropertyGroup>
<ReadLinesFromFile File="$(PaketCliToolFilePath)" >
<Output TaskParameter="Lines" ItemName="PaketCliToolFileLines"/>
</ReadLinesFromFile>
<ItemGroup Condition=" '@(PaketCliToolFileLines)' != '' " >
<PaketCliToolFileLinesInfo Include="@(PaketCliToolFileLines)" >
<PackageName>$([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0])</PackageName>
<PackageVersion>$([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1])</PackageVersion>
</PaketCliToolFileLinesInfo>
<DotNetCliToolReference Include="%(PaketCliToolFileLinesInfo.PackageName)">
<Version>%(PaketCliToolFileLinesInfo.PackageVersion)</Version>
</DotNetCliToolReference>
</ItemGroup>
<!-- Disabled for now until we know what to do with runtime deps - https://github.com/fsprojects/Paket/issues/2964
<PropertyGroup>
<RestoreConfigFile>$(PaketIntermediateOutputPath)/$(MSBuildProjectFile).NuGet.Config</RestoreConfigFile>
</PropertyGroup> -->
</Target>
<Target Name="PaketDisableDirectPack" AfterTargets="_IntermediatePack" BeforeTargets="GenerateNuspec" Condition="('$(IsPackable)' == '' Or '$(IsPackable)' == 'true') And Exists('$(PaketIntermediateOutputPath)/$(MSBuildProjectFile).references')" >
<PropertyGroup>
<ContinuePackingAfterGeneratingNuspec>false</ContinuePackingAfterGeneratingNuspec>
</PropertyGroup>
</Target>
<Target Name="PaketOverrideNuspec" DependsOnTargets="SetPaketCommand" AfterTargets="GenerateNuspec" Condition="('$(IsPackable)' == '' Or '$(IsPackable)' == 'true') And Exists('$(PaketIntermediateOutputPath)/$(MSBuildProjectFile).references')" >
<ItemGroup>
<_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/>
<MSBuildMajorVersion Include="$(DetectedMSBuildVersion.Replace(`-`, `.`).Split(`.`)[0])" />
<MSBuildMinorVersion Include="$(DetectedMSBuildVersion.Replace(`-`, `.`).Split(`.`)[1])" />
</ItemGroup>
<PropertyGroup>
<PaketProjectFile>$(MSBuildProjectDirectory)/$(MSBuildProjectFile)</PaketProjectFile>
<ContinuePackingAfterGeneratingNuspec>true</ContinuePackingAfterGeneratingNuspec>
<UseMSBuild16_0_Pack>false</UseMSBuild16_0_Pack>
<UseMSBuild16_0_Pack Condition=" '@(MSBuildMajorVersion)' >= '16' ">true</UseMSBuild16_0_Pack>
<UseMSBuild15_9_Pack>false</UseMSBuild15_9_Pack>
<UseMSBuild15_9_Pack Condition=" '@(MSBuildMajorVersion)' == '15' AND '@(MSBuildMinorVersion)' > '8' ">true</UseMSBuild15_9_Pack>
<UseMSBuild15_8_Pack>false</UseMSBuild15_8_Pack>
<UseMSBuild15_8_Pack Condition=" '$(NuGetToolVersion)' != '4.0.0' AND (! $(UseMSBuild15_9_Pack)) AND (! $(UseMSBuild16_0_Pack)) ">true</UseMSBuild15_8_Pack>
<UseNuGet4_Pack>false</UseNuGet4_Pack>
<UseNuGet4_Pack Condition=" (! $(UseMSBuild15_8_Pack)) AND (! $(UseMSBuild15_9_Pack)) AND (! $(UseMSBuild16_0_Pack)) ">true</UseNuGet4_Pack>
<AdjustedNuspecOutputPath>$(PaketIntermediateOutputPath)\$(Configuration)</AdjustedNuspecOutputPath>
<AdjustedNuspecOutputPath Condition="@(_NuspecFilesNewLocation) == ''">$(PaketIntermediateOutputPath)</AdjustedNuspecOutputPath>
</PropertyGroup>
<ItemGroup>
<_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/>
</ItemGroup>
<Error Text="Error Because of PAKET_ERROR_ON_MSBUILD_EXEC (not calling fix-nuspecs)" Condition=" '$(PAKET_ERROR_ON_MSBUILD_EXEC)' == 'true' " />
<Exec Condition="@(_NuspecFiles) != ''" Command='$(PaketCommand) fix-nuspecs files "@(_NuspecFiles)" project-file "$(PaketProjectFile)" ' />
<Error Condition="@(_NuspecFiles) == ''" Text='Could not find nuspec files in "$(AdjustedNuspecOutputPath)" (Version: "$(PackageVersion)"), therefore we cannot call "paket fix-nuspecs" and have to error out!' />
<ConvertToAbsolutePath Condition="@(_NuspecFiles) != ''" Paths="@(_NuspecFiles)">
<Output TaskParameter="AbsolutePaths" PropertyName="NuspecFileAbsolutePath" />
</ConvertToAbsolutePath>
<!-- Call Pack -->
<PackTask Condition="$(UseMSBuild16_0_Pack)"
PackItem="$(PackProjectInputFile)"
PackageFiles="@(_PackageFiles)"
PackageFilesToExclude="@(_PackageFilesToExclude)"
PackageVersion="$(PackageVersion)"
PackageId="$(PackageId)"
Title="$(Title)"
Authors="$(Authors)"
Description="$(Description)"
Copyright="$(Copyright)"
RequireLicenseAcceptance="$(PackageRequireLicenseAcceptance)"
LicenseUrl="$(PackageLicenseUrl)"
ProjectUrl="$(PackageProjectUrl)"
IconUrl="$(PackageIconUrl)"
ReleaseNotes="$(PackageReleaseNotes)"
Tags="$(PackageTags)"
DevelopmentDependency="$(DevelopmentDependency)"
BuildOutputInPackage="@(_BuildOutputInPackage)"
TargetPathsToSymbols="@(_TargetPathsToSymbols)"
SymbolPackageFormat="$(SymbolPackageFormat)"
TargetFrameworks="@(_TargetFrameworks)"
AssemblyName="$(AssemblyName)"
PackageOutputPath="$(PackageOutputAbsolutePath)"
IncludeSymbols="$(IncludeSymbols)"
IncludeSource="$(IncludeSource)"
PackageTypes="$(PackageType)"
IsTool="$(IsTool)"
RepositoryUrl="$(RepositoryUrl)"
RepositoryType="$(RepositoryType)"
SourceFiles="@(_SourceFiles->Distinct())"
NoPackageAnalysis="$(NoPackageAnalysis)"
MinClientVersion="$(MinClientVersion)"
Serviceable="$(Serviceable)"
FrameworkAssemblyReferences="@(_FrameworkAssemblyReferences)"
ContinuePackingAfterGeneratingNuspec="$(ContinuePackingAfterGeneratingNuspec)"
NuspecOutputPath="$(AdjustedNuspecOutputPath)"
IncludeBuildOutput="$(IncludeBuildOutput)"
BuildOutputFolders="$(BuildOutputTargetFolder)"
ContentTargetFolders="$(ContentTargetFolders)"
RestoreOutputPath="$(RestoreOutputAbsolutePath)"
NuspecFile="$(NuspecFileAbsolutePath)"
NuspecBasePath="$(NuspecBasePath)"
NuspecProperties="$(NuspecProperties)"
PackageLicenseFile="$(PackageLicenseFile)"
PackageLicenseExpression="$(PackageLicenseExpression)"
PackageLicenseExpressionVersion="$(PackageLicenseExpressionVersion)" />
<PackTask Condition="$(UseMSBuild15_9_Pack)"
PackItem="$(PackProjectInputFile)"
PackageFiles="@(_PackageFiles)"
PackageFilesToExclude="@(_PackageFilesToExclude)"
PackageVersion="$(PackageVersion)"
PackageId="$(PackageId)"
Title="$(Title)"
Authors="$(Authors)"
Description="$(Description)"
Copyright="$(Copyright)"
RequireLicenseAcceptance="$(PackageRequireLicenseAcceptance)"
LicenseUrl="$(PackageLicenseUrl)"
ProjectUrl="$(PackageProjectUrl)"
IconUrl="$(PackageIconUrl)"
ReleaseNotes="$(PackageReleaseNotes)"
Tags="$(PackageTags)"
DevelopmentDependency="$(DevelopmentDependency)"
BuildOutputInPackage="@(_BuildOutputInPackage)"
TargetPathsToSymbols="@(_TargetPathsToSymbols)"
SymbolPackageFormat="$(SymbolPackageFormat)"
TargetFrameworks="@(_TargetFrameworks)"
AssemblyName="$(AssemblyName)"
PackageOutputPath="$(PackageOutputAbsolutePath)"
IncludeSymbols="$(IncludeSymbols)"
IncludeSource="$(IncludeSource)"
PackageTypes="$(PackageType)"
IsTool="$(IsTool)"
RepositoryUrl="$(RepositoryUrl)"
RepositoryType="$(RepositoryType)"
SourceFiles="@(_SourceFiles->Distinct())"
NoPackageAnalysis="$(NoPackageAnalysis)"
MinClientVersion="$(MinClientVersion)"
Serviceable="$(Serviceable)"
FrameworkAssemblyReferences="@(_FrameworkAssemblyReferences)"
ContinuePackingAfterGeneratingNuspec="$(ContinuePackingAfterGeneratingNuspec)"
NuspecOutputPath="$(AdjustedNuspecOutputPath)"
IncludeBuildOutput="$(IncludeBuildOutput)"
BuildOutputFolder="$(BuildOutputTargetFolder)"
ContentTargetFolders="$(ContentTargetFolders)"
RestoreOutputPath="$(RestoreOutputAbsolutePath)"
NuspecFile="$(NuspecFileAbsolutePath)"
NuspecBasePath="$(NuspecBasePath)"
NuspecProperties="$(NuspecProperties)"/>
<PackTask Condition="$(UseMSBuild15_8_Pack)"
PackItem="$(PackProjectInputFile)"
PackageFiles="@(_PackageFiles)"
PackageFilesToExclude="@(_PackageFilesToExclude)"
PackageVersion="$(PackageVersion)"
PackageId="$(PackageId)"
Title="$(Title)"
Authors="$(Authors)"
Description="$(Description)"
Copyright="$(Copyright)"
RequireLicenseAcceptance="$(PackageRequireLicenseAcceptance)"
LicenseUrl="$(PackageLicenseUrl)"
ProjectUrl="$(PackageProjectUrl)"
IconUrl="$(PackageIconUrl)"
ReleaseNotes="$(PackageReleaseNotes)"
Tags="$(PackageTags)"
DevelopmentDependency="$(DevelopmentDependency)"
BuildOutputInPackage="@(_BuildOutputInPackage)"
TargetPathsToSymbols="@(_TargetPathsToSymbols)"
TargetFrameworks="@(_TargetFrameworks)"
AssemblyName="$(AssemblyName)"
PackageOutputPath="$(PackageOutputAbsolutePath)"
IncludeSymbols="$(IncludeSymbols)"
IncludeSource="$(IncludeSource)"
PackageTypes="$(PackageType)"
IsTool="$(IsTool)"
RepositoryUrl="$(RepositoryUrl)"
RepositoryType="$(RepositoryType)"
SourceFiles="@(_SourceFiles->Distinct())"
NoPackageAnalysis="$(NoPackageAnalysis)"
MinClientVersion="$(MinClientVersion)"
Serviceable="$(Serviceable)"
FrameworkAssemblyReferences="@(_FrameworkAssemblyReferences)"
ContinuePackingAfterGeneratingNuspec="$(ContinuePackingAfterGeneratingNuspec)"
NuspecOutputPath="$(AdjustedNuspecOutputPath)"
IncludeBuildOutput="$(IncludeBuildOutput)"
BuildOutputFolder="$(BuildOutputTargetFolder)"
ContentTargetFolders="$(ContentTargetFolders)"
RestoreOutputPath="$(RestoreOutputAbsolutePath)"
NuspecFile="$(NuspecFileAbsolutePath)"
NuspecBasePath="$(NuspecBasePath)"
NuspecProperties="$(NuspecProperties)"/>
<PackTask Condition="$(UseNuGet4_Pack)"
PackItem="$(PackProjectInputFile)"
PackageFiles="@(_PackageFiles)"
PackageFilesToExclude="@(_PackageFilesToExclude)"
PackageVersion="$(PackageVersion)"
PackageId="$(PackageId)"
Title="$(Title)"
Authors="$(Authors)"
Description="$(Description)"
Copyright="$(Copyright)"
RequireLicenseAcceptance="$(PackageRequireLicenseAcceptance)"
LicenseUrl="$(PackageLicenseUrl)"
ProjectUrl="$(PackageProjectUrl)"
IconUrl="$(PackageIconUrl)"
ReleaseNotes="$(PackageReleaseNotes)"
Tags="$(PackageTags)"
TargetPathsToAssemblies="@(_TargetPathsToAssemblies->'%(FinalOutputPath)')"
TargetPathsToSymbols="@(_TargetPathsToSymbols)"
TargetFrameworks="@(_TargetFrameworks)"
AssemblyName="$(AssemblyName)"
PackageOutputPath="$(PackageOutputAbsolutePath)"
IncludeSymbols="$(IncludeSymbols)"
IncludeSource="$(IncludeSource)"
PackageTypes="$(PackageType)"
IsTool="$(IsTool)"
RepositoryUrl="$(RepositoryUrl)"
RepositoryType="$(RepositoryType)"
SourceFiles="@(_SourceFiles->Distinct())"
NoPackageAnalysis="$(NoPackageAnalysis)"
MinClientVersion="$(MinClientVersion)"
Serviceable="$(Serviceable)"
AssemblyReferences="@(_References)"
ContinuePackingAfterGeneratingNuspec="$(ContinuePackingAfterGeneratingNuspec)"
NuspecOutputPath="$(AdjustedNuspecOutputPath)"
IncludeBuildOutput="$(IncludeBuildOutput)"
BuildOutputFolder="$(BuildOutputTargetFolder)"
ContentTargetFolders="$(ContentTargetFolders)"
RestoreOutputPath="$(RestoreOutputAbsolutePath)"
NuspecFile="$(NuspecFileAbsolutePath)"
NuspecBasePath="$(NuspecBasePath)"
NuspecProperties="$(NuspecProperties)"/>
</Target>
<!--/+:cnd:noEmit-->
</Project>

BIN
.paket/paket.exe Normal file

Binary file not shown.

2
.yarnrc Normal file
View File

@@ -0,0 +1,2 @@
network-timeout 50000000
strict-ssl false

0
Contributing.md Normal file
View File

208
Fable.SignalR.sln Normal file
View File

@@ -0,0 +1,208 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29009.5
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{63297B98-5CED-492C-A5B7-A5B4F73CF142}"
ProjectSection(SolutionItems) = preProject
Nuget.Config = Nuget.Config
paket.dependencies = paket.dependencies
paket.lock = paket.lock
paket.references = paket.references
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "project", "project", "{BF60BC93-E09B-4E5F-9D85-95A519479D54}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.github\ISSUE_TEMPLATE\BUG_REPORT.md = .github\ISSUE_TEMPLATE\BUG_REPORT.md
.github\ISSUE_TEMPLATE\FEATURE_REQUEST.md = .github\ISSUE_TEMPLATE\FEATURE_REQUEST.md
.github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md
README.md = README.md
RELEASE_NOTES.md = RELEASE_NOTES.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "git", "git", "{078A9C52-DDC1-46F4-9235-9E6C89C87AFD}"
ProjectSection(SolutionItems) = preProject
.gitattributes = .gitattributes
.gitignore = .gitignore
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".build", ".build", "{7C6D08E7-3EAC-4335-8F4B-252C193C27C9}"
ProjectSection(SolutionItems) = preProject
build.cmd = build.cmd
build.fsx = build.fsx
build.proj = build.proj
build.sh = build.sh
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{55637037-300B-4FA5-8D14-B2CECCCD4B0D}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "tools", "tools\tools.fsproj", "{E03A3045-EEAB-4616-B0D8-A50A1B75F72B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "web", "web", "{7F29BFE8-02B3-4763-838C-3D8228334B4B}"
ProjectSection(SolutionItems) = preProject
.yarnrc = .yarnrc
package.json = package.json
publish.js = publish.js
webpack.config.js = webpack.config.js
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{27F9F1A6-C6B4-4539-B013-8549B4A6E1B5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{D05F59F8-14B6-44AB-8FE6-493EE0CA4A75}"
ProjectSection(SolutionItems) = preProject
docs\acknowledgment.md = docs\acknowledgment.md
docs\contributing.md = docs\contributing.md
docs\creating-tests.md = docs\creating-tests.md
docs\index.html = docs\index.html
docs\README.md = docs\README.md
docs\running-tests.md = docs\running-tests.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "installation", "installation", "{BE0829CD-A553-4C7B-AF61-146B1732B44E}"
ProjectSection(SolutionItems) = preProject
docs\installation\fast-check-jest.md = docs\installation\fast-check-jest.md
docs\installation\fast-check.md = docs\installation\fast-check.md
docs\installation\jest.md = docs\installation\jest.md
docs\installation\react-testing-library.md = docs\installation\react-testing-library.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "jest", "jest", "{3312B9A9-80A1-4CE8-AC6F-708A2DDF2D41}"
ProjectSection(SolutionItems) = preProject
docs\jest\describe.md = docs\jest\describe.md
docs\jest\expect-helpers.md = docs\jest\expect-helpers.md
docs\jest\expect.md = docs\jest\expect.md
docs\jest\globals.md = docs\jest\globals.md
docs\jest\README.md = docs\jest\README.md
docs\jest\test.md = docs\jest\test.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rtl", "rtl", "{FB9E5277-7525-4AD5-92D6-DFEC55530DC2}"
ProjectSection(SolutionItems) = preProject
docs\rtl\queries-for-element.md = docs\rtl\queries-for-element.md
docs\rtl\README.md = docs\rtl\README.md
docs\rtl\render.md = docs\rtl\render.md
docs\rtl\rtl.md = docs\rtl\rtl.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "styles", "styles", "{80A18350-F484-458A-BAC0-64EE73830C5F}"
ProjectSection(SolutionItems) = preProject
docs\styles\website.css = docs\styles\website.css
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fast-check", "fast-check", "{DCC97AAF-315E-45E7-BB43-CDACB5313758}"
ProjectSection(SolutionItems) = preProject
docs\fast-check\elmish-model-testing.md = docs\fast-check\elmish-model-testing.md
docs\fast-check\fast-check.md = docs\fast-check\fast-check.md
docs\fast-check\jest-extension.md = docs\fast-check\jest-extension.md
docs\fast-check\model-testing.md = docs\fast-check\model-testing.md
docs\fast-check\README.md = docs\fast-check\README.md
docs\fast-check\scheduler.md = docs\fast-check\scheduler.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "arbitrary", "arbitrary", "{C8B206F1-2629-47F1-A6C6-255718289E88}"
ProjectSection(SolutionItems) = preProject
docs\fast-check\arbitrary\array.md = docs\fast-check\arbitrary\array.md
docs\fast-check\arbitrary\ce.md = docs\fast-check\arbitrary\ce.md
docs\fast-check\arbitrary\constrained-defaults.md = docs\fast-check\arbitrary\constrained-defaults.md
docs\fast-check\arbitrary\defaults.md = docs\fast-check\arbitrary\defaults.md
docs\fast-check\arbitrary\list.md = docs\fast-check\arbitrary\list.md
docs\fast-check\arbitrary\map.md = docs\fast-check\arbitrary\map.md
docs\fast-check\arbitrary\README.md = docs\fast-check\arbitrary\README.md
docs\fast-check\arbitrary\resizearray.md = docs\fast-check\arbitrary\resizearray.md
docs\fast-check\arbitrary\seq.md = docs\fast-check\arbitrary\seq.md
docs\fast-check\arbitrary\set.md = docs\fast-check\arbitrary\set.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{EC117A2E-CAE1-4143-81F8-6F6B41AC48B4}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Client", "demo\Client\Client.fsproj", "{0F05C164-FC1E-4FC1-B213-3ED677592D25}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Server", "demo\Server\Server.fsproj", "{FE5C8B58-E228-404F-9EA8-E4808CAEE9D4}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Shared", "demo\Shared\Shared.fsproj", "{59F17747-6D36-474F-B11E-3D904C511587}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fable.SignalR", "src\Fable.SignalR\Fable.SignalR.fsproj", "{284463AC-094F-46D1-9DD2-03DF87CAF73B}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fable.SignalR.Tests", "tests\Fable.SignalR.Tests\Fable.SignalR.Tests.fsproj", "{20AEA7CD-4388-4F1B-ADA5-B547CE69709A}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fable.SignalR.Saturn", "src\Fable.SignalR.Saturn\Fable.SignalR.Saturn.fsproj", "{28889A5B-0211-4D11-BED1-E1CAC75AC56A}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fable.SignalR.AspNetCore", "src\Fable.SignalR.AspNetCore\Fable.SignalR.AspNetCore.fsproj", "{70E1E507-275B-4BB6-9819-131A4A58E0C6}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fable.SignalR.Server", "src\Fable.SignalR.Server\Fable.SignalR.Server.fsproj", "{AF3591D6-E283-40BB-91E9-31059BCC5DE7}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fable.SignalR.Elmish", "src\Fable.SignalR.Elmish\Fable.SignalR.Elmish.fsproj", "{7FBCC628-33B0-42E7-BE7C-77BB089B5E1F}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fable.SignalR.Feliz", "src\Fable.SignalR.Feliz\Fable.SignalR.Feliz.fsproj", "{07054AA0-E8D5-4CD2-A774-A515CB4F2EBD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E03A3045-EEAB-4616-B0D8-A50A1B75F72B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E03A3045-EEAB-4616-B0D8-A50A1B75F72B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E03A3045-EEAB-4616-B0D8-A50A1B75F72B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E03A3045-EEAB-4616-B0D8-A50A1B75F72B}.Release|Any CPU.Build.0 = Release|Any CPU
{0F05C164-FC1E-4FC1-B213-3ED677592D25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F05C164-FC1E-4FC1-B213-3ED677592D25}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F05C164-FC1E-4FC1-B213-3ED677592D25}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F05C164-FC1E-4FC1-B213-3ED677592D25}.Release|Any CPU.Build.0 = Release|Any CPU
{FE5C8B58-E228-404F-9EA8-E4808CAEE9D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE5C8B58-E228-404F-9EA8-E4808CAEE9D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE5C8B58-E228-404F-9EA8-E4808CAEE9D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE5C8B58-E228-404F-9EA8-E4808CAEE9D4}.Release|Any CPU.Build.0 = Release|Any CPU
{59F17747-6D36-474F-B11E-3D904C511587}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{59F17747-6D36-474F-B11E-3D904C511587}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59F17747-6D36-474F-B11E-3D904C511587}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59F17747-6D36-474F-B11E-3D904C511587}.Release|Any CPU.Build.0 = Release|Any CPU
{284463AC-094F-46D1-9DD2-03DF87CAF73B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{284463AC-094F-46D1-9DD2-03DF87CAF73B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{284463AC-094F-46D1-9DD2-03DF87CAF73B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{284463AC-094F-46D1-9DD2-03DF87CAF73B}.Release|Any CPU.Build.0 = Release|Any CPU
{20AEA7CD-4388-4F1B-ADA5-B547CE69709A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{20AEA7CD-4388-4F1B-ADA5-B547CE69709A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{20AEA7CD-4388-4F1B-ADA5-B547CE69709A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{20AEA7CD-4388-4F1B-ADA5-B547CE69709A}.Release|Any CPU.Build.0 = Release|Any CPU
{28889A5B-0211-4D11-BED1-E1CAC75AC56A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{28889A5B-0211-4D11-BED1-E1CAC75AC56A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{28889A5B-0211-4D11-BED1-E1CAC75AC56A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{28889A5B-0211-4D11-BED1-E1CAC75AC56A}.Release|Any CPU.Build.0 = Release|Any CPU
{70E1E507-275B-4BB6-9819-131A4A58E0C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70E1E507-275B-4BB6-9819-131A4A58E0C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70E1E507-275B-4BB6-9819-131A4A58E0C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70E1E507-275B-4BB6-9819-131A4A58E0C6}.Release|Any CPU.Build.0 = Release|Any CPU
{AF3591D6-E283-40BB-91E9-31059BCC5DE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF3591D6-E283-40BB-91E9-31059BCC5DE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF3591D6-E283-40BB-91E9-31059BCC5DE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF3591D6-E283-40BB-91E9-31059BCC5DE7}.Release|Any CPU.Build.0 = Release|Any CPU
{7FBCC628-33B0-42E7-BE7C-77BB089B5E1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7FBCC628-33B0-42E7-BE7C-77BB089B5E1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FBCC628-33B0-42E7-BE7C-77BB089B5E1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FBCC628-33B0-42E7-BE7C-77BB089B5E1F}.Release|Any CPU.Build.0 = Release|Any CPU
{07054AA0-E8D5-4CD2-A774-A515CB4F2EBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07054AA0-E8D5-4CD2-A774-A515CB4F2EBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07054AA0-E8D5-4CD2-A774-A515CB4F2EBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07054AA0-E8D5-4CD2-A774-A515CB4F2EBD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{E03A3045-EEAB-4616-B0D8-A50A1B75F72B} = {55637037-300B-4FA5-8D14-B2CECCCD4B0D}
{BE0829CD-A553-4C7B-AF61-146B1732B44E} = {D05F59F8-14B6-44AB-8FE6-493EE0CA4A75}
{3312B9A9-80A1-4CE8-AC6F-708A2DDF2D41} = {D05F59F8-14B6-44AB-8FE6-493EE0CA4A75}
{FB9E5277-7525-4AD5-92D6-DFEC55530DC2} = {D05F59F8-14B6-44AB-8FE6-493EE0CA4A75}
{80A18350-F484-458A-BAC0-64EE73830C5F} = {D05F59F8-14B6-44AB-8FE6-493EE0CA4A75}
{DCC97AAF-315E-45E7-BB43-CDACB5313758} = {D05F59F8-14B6-44AB-8FE6-493EE0CA4A75}
{C8B206F1-2629-47F1-A6C6-255718289E88} = {DCC97AAF-315E-45E7-BB43-CDACB5313758}
{0F05C164-FC1E-4FC1-B213-3ED677592D25} = {EC117A2E-CAE1-4143-81F8-6F6B41AC48B4}
{FE5C8B58-E228-404F-9EA8-E4808CAEE9D4} = {EC117A2E-CAE1-4143-81F8-6F6B41AC48B4}
{59F17747-6D36-474F-B11E-3D904C511587} = {EC117A2E-CAE1-4143-81F8-6F6B41AC48B4}
{20AEA7CD-4388-4F1B-ADA5-B547CE69709A} = {27F9F1A6-C6B4-4539-B013-8549B4A6E1B5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {96DAE6A9-B1CC-4FF7-B08C-D5FBFD55B385}
EndGlobalSection
EndGlobal

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Shmew
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

11
Nuget.Config Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- This clears Nuget configuration in the machine to avoid conflicts with Paket -->
<packageSources>
<clear />
<add key="NuGet.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<disabledPackageSources>
<clear />
</disabledPackageSources>
</configuration>

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
# Fable.SignalR [![Nuget](https://img.shields.io/nuget/v/Fable.SignalR.svg?maxAge=0&colorB=brightgreen&label=Fable.SignalR)](https://www.nuget.org/packages/Fable.SignalR)
A quick look:
```fsharp
```
Full documentation can be found [here](https://shmew.github.io/Fable.SignalR).

2
RELEASE_NOTES.md Normal file
View File

@@ -0,0 +1,2 @@
### 0.0.1 - Wednesday, March 25, 2020
* Initial build

6
build.cmd Normal file
View File

@@ -0,0 +1,6 @@
@echo off
cls
dotnet restore build.proj
fake build %*

491
build.fsx Normal file
View File

@@ -0,0 +1,491 @@
// --------------------------------------------------------------------------------------
// FAKE build script
// --------------------------------------------------------------------------------------
#nowarn "0213"
#r "paket: groupref FakeBuild //"
#load "./tools/FSharpLint.fs"
#load "./tools/Web.fs"
#load "./.fake/build.fsx/intellisense.fsx"
open Fake.Core
open Fake.Core.TargetOperators
open Fake.DotNet
open Fake.JavaScript
open Fake.IO
open Fake.IO.FileSystemOperators
open Fake.IO.Globbing.Operators
open Fake.Tools
open Tools.Linting
open Tools.Web
open System
open System.IO
// The name of the project
// (used by attributes in AssemblyInfo, name of a NuGet package and directory in 'src')
let project = "Fable.SignalR"
// Short summary of the project
// (used as description in AssemblyInfo and as a short summary for NuGet package)
let summary = "Fable and server bindings for SignalR."
// Author(s) of the project
let author = "Cody Johnson"
// File system information
let solutionFile = "Fable.SignalR.sln"
// Github repo
let repo = "https://github.com/Shmew/Fable.SignalR"
// Files that have bindings to other languages where name linting needs to be more relaxed.
let relaxedNameLinting =
[ __SOURCE_DIRECTORY__ @@ "src/Fable.SignalR/*.fs"
__SOURCE_DIRECTORY__ @@ "src/Fable.SignalR.Elmish/*.fs"
__SOURCE_DIRECTORY__ @@ "src/Fable.SignalR.Feliz/*.fs" ]
// Read additional information from the release notes document
let release = ReleaseNotes.load (__SOURCE_DIRECTORY__ @@ "RELEASE_NOTES.md")
// Helper active pattern for project types
let (|Fsproj|Csproj|Vbproj|Shproj|) (projFileName:string) =
match projFileName with
| f when f.EndsWith("fsproj") -> Fsproj
| f when f.EndsWith("csproj") -> Csproj
| f when f.EndsWith("vbproj") -> Vbproj
| f when f.EndsWith("shproj") -> Shproj
| _ -> failwith (sprintf "Project file %s not supported. Unknown project type." projFileName)
let srcGlob = __SOURCE_DIRECTORY__ @@ "src/**/*.??proj"
let fsSrcGlob = __SOURCE_DIRECTORY__ @@ "src/**/*.fs"
let fsTestGlob = __SOURCE_DIRECTORY__ @@ "tests/**/*.fs"
let bin = __SOURCE_DIRECTORY__ @@ "bin"
let docs = __SOURCE_DIRECTORY__ @@ "docs"
let temp = __SOURCE_DIRECTORY__ @@ "temp"
let objFolder = __SOURCE_DIRECTORY__ @@ "obj"
let dist = __SOURCE_DIRECTORY__ @@ "dist"
let libGlob = __SOURCE_DIRECTORY__ @@ "src/**/*.fsproj"
let demoGlob = __SOURCE_DIRECTORY__ @@ "demo/**/*.fsproj"
let foldExcludeGlobs (g: IGlobbingPattern) (d: string) = g -- d
let foldIncludeGlobs (g: IGlobbingPattern) (d: string) = g ++ d
let fsSrcAndTest =
!! fsSrcGlob
++ fsTestGlob
-- (__SOURCE_DIRECTORY__ @@ "src/**/obj/**")
-- (__SOURCE_DIRECTORY__ @@ "tests/**/obj/**")
-- (__SOURCE_DIRECTORY__ @@ "src/**/AssemblyInfo.*")
-- (__SOURCE_DIRECTORY__ @@ "src/**/**/AssemblyInfo.*")
let fsRelaxedNameLinting =
let baseGlob s =
!! s
-- (__SOURCE_DIRECTORY__ @@ "src/**/AssemblyInfo.*")
-- (__SOURCE_DIRECTORY__ @@ "src/**/obj/**")
-- (__SOURCE_DIRECTORY__ @@ "tests/**/obj/**")
match relaxedNameLinting with
| [h] when relaxedNameLinting.Length = 1 -> baseGlob h |> Some
| h::t -> List.fold foldIncludeGlobs (baseGlob h) t |> Some
| _ -> None
let configuration() =
FakeVar.getOrDefault "configuration" "Release"
let getEnvFromAllOrNone (s: string) =
let envOpt (envVar: string) =
if String.isNullOrEmpty envVar then None
else Some(envVar)
let procVar = Environment.GetEnvironmentVariable(s) |> envOpt
let userVar = Environment.GetEnvironmentVariable(s, EnvironmentVariableTarget.User) |> envOpt
let machVar = Environment.GetEnvironmentVariable(s, EnvironmentVariableTarget.Machine) |> envOpt
match procVar,userVar,machVar with
| Some(v), _, _
| _, Some(v), _
| _, _, Some(v)
-> Some(v)
| _ -> None
// Set default
FakeVar.set "configuration" "Release"
// --------------------------------------------------------------------------------------
// Set configuration mode based on target
Target.create "ConfigDebug" <| fun _ ->
FakeVar.set "configuration" "Debug"
Target.create "ConfigRelease" <| fun _ ->
FakeVar.set "configuration" "Release"
// --------------------------------------------------------------------------------------
// Generate assembly info files with the right version & up-to-date information
Target.create "AssemblyInfo" <| fun _ ->
let getAssemblyInfoAttributes projectName =
[ AssemblyInfo.Title (projectName)
AssemblyInfo.Product project
AssemblyInfo.Description summary
AssemblyInfo.Version release.AssemblyVersion
AssemblyInfo.FileVersion release.AssemblyVersion
AssemblyInfo.Configuration <| configuration()
AssemblyInfo.InternalsVisibleTo (sprintf "%s.Tests" projectName) ]
let getProjectDetails projectPath =
let projectName = Path.GetFileNameWithoutExtension(projectPath)
( projectPath,
projectName,
Path.GetDirectoryName(projectPath),
(getAssemblyInfoAttributes projectName)
)
!! srcGlob
|> Seq.map getProjectDetails
|> Seq.iter (fun (projFileName, _, folderName, attributes) ->
match projFileName with
| Fsproj -> AssemblyInfoFile.createFSharp (folderName </> "AssemblyInfo.fs") attributes
| Csproj -> AssemblyInfoFile.createCSharp ((folderName </> "Properties") </> "AssemblyInfo.cs") attributes
| Vbproj -> AssemblyInfoFile.createVisualBasic ((folderName </> "My Project") </> "AssemblyInfo.vb") attributes
| Shproj -> () )
// --------------------------------------------------------------------------------------
// Copies binaries from default VS location to expected bin folder
// But keeps a subdirectory structure for each project in the
// src folder to support multiple project outputs
Target.create "CopyBinaries" <| fun _ ->
!! libGlob
-- (__SOURCE_DIRECTORY__ @@ "src/**/*.shproj")
|> Seq.map (fun f -> ((Path.getDirectory f) @@ "bin" @@ configuration(), "bin" @@ (Path.GetFileNameWithoutExtension f)))
|> Seq.iter (fun (fromDir, toDir) -> Shell.copyDir toDir fromDir (fun _ -> true))
// --------------------------------------------------------------------------------------
// Clean tasks
Target.create "Clean" <| fun _ ->
let clean() =
!! (__SOURCE_DIRECTORY__ @@ "tests/**/bin")
++ (__SOURCE_DIRECTORY__ @@ "tests/**/obj")
++ (__SOURCE_DIRECTORY__ @@ "tools/bin")
++ (__SOURCE_DIRECTORY__ @@ "tools/obj")
++ (__SOURCE_DIRECTORY__ @@ "src/**/bin")
++ (__SOURCE_DIRECTORY__ @@ "src/**/obj")
|> Seq.toList
|> List.append [bin; temp; objFolder; dist]
|> Shell.cleanDirs
TaskRunner.runWithRetries clean 10
Target.create "CleanDocs" <| fun _ ->
let clean() =
!! (docs @@ "RELEASE_NOTES.md")
|> List.ofSeq
|> List.iter Shell.rm
TaskRunner.runWithRetries clean 10
Target.create "CopyDocFiles" <| fun _ ->
[ docs @@ "RELEASE_NOTES.md", __SOURCE_DIRECTORY__ @@ "RELEASE_NOTES.md" ]
|> List.iter (fun (target, source) -> Shell.copyFile target source)
Target.create "PrepDocs" ignore
Target.create "PostBuildClean" <| fun _ ->
let clean() =
!! srcGlob
-- (__SOURCE_DIRECTORY__ @@ "src/**/*.shproj")
|> Seq.map (
(fun f -> (Path.getDirectory f) @@ "bin" @@ configuration())
>> (fun f -> Directory.EnumerateDirectories(f) |> Seq.toList )
>> (fun fL -> fL |> List.map (fun f -> Directory.EnumerateDirectories(f) |> Seq.toList)))
|> (Seq.concat >> Seq.concat)
|> Seq.iter Directory.delete
TaskRunner.runWithRetries clean 10
Target.create "PostPublishClean" <| fun _ ->
let clean() =
!! (__SOURCE_DIRECTORY__ @@ "src/**/bin" @@ configuration() @@ "/**/publish")
|> Seq.iter Directory.delete
TaskRunner.runWithRetries clean 10
// --------------------------------------------------------------------------------------
// Restore tasks
let restoreSolution () =
solutionFile
|> DotNet.restore id
Target.create "Restore" <| fun _ ->
TaskRunner.runWithRetries restoreSolution 5
Target.create "YarnInstall" <| fun _ ->
let setParams (defaults:Yarn.YarnParams) =
{ defaults with
Yarn.YarnParams.YarnFilePath = (__SOURCE_DIRECTORY__ @@ "packages/tooling/Yarnpkg.Yarn/content/bin/yarn.cmd")
}
Yarn.install setParams
Target.create "RebuildSass" <| fun _ ->
Npm.exec "rebuild node-sass" id
// --------------------------------------------------------------------------------------
// Build tasks
Target.create "Build" <| fun _ ->
let setParams (defaults:MSBuildParams) =
{ defaults with
Verbosity = Some(Quiet)
Targets = ["Build"]
Properties =
[
"Optimize", "True"
"DebugSymbols", "True"
"Configuration", configuration()
"Version", release.AssemblyVersion
"GenerateDocumentationFile", "true"
"DependsOnNETStandard", "true"
]
}
restoreSolution()
!! libGlob
++ demoGlob
|> List.ofSeq
|> List.iter (MSBuild.build setParams)
// --------------------------------------------------------------------------------------
// Publish net core applications
Target.create "PublishDotNet" <| fun _ ->
let runPublish (project: string) (framework: string) =
let setParams (defaults:MSBuildParams) =
{ defaults with
Verbosity = Some(Quiet)
Targets = ["Publish"]
Properties =
[
"Optimize", "True"
"DebugSymbols", "True"
"Configuration", configuration()
"Version", release.AssemblyVersion
"GenerateDocumentationFile", "true"
"TargetFramework", framework
]
}
MSBuild.build setParams project
!! libGlob
++ demoGlob
|> Seq.map
((fun f -> (((Path.getDirectory f) @@ "bin" @@ configuration()), f) )
>>
(fun f ->
Directory.EnumerateDirectories(fst f)
|> Seq.filter (fun frFolder -> frFolder.Contains("netcoreapp"))
|> Seq.map (fun frFolder -> DirectoryInfo(frFolder).Name), snd f))
|> Seq.iter (fun (l,p) -> l |> Seq.iter (runPublish p))
// --------------------------------------------------------------------------------------
// Lint source code
Target.create "Lint" <| fun _ ->
fsSrcAndTest
-- (__SOURCE_DIRECTORY__ @@ "src/**/AssemblyInfo.*")
|> (fun src -> List.fold foldExcludeGlobs src relaxedNameLinting)
|> (fun fGlob ->
match fsRelaxedNameLinting with
| Some(glob) ->
[(false, fGlob); (true, glob)]
| None -> [(false, fGlob)])
|> Seq.map (fun (b,glob) -> (b,glob |> List.ofSeq))
|> List.ofSeq
|> FSharpLinter.lintFiles
// --------------------------------------------------------------------------------------
// Run the unit tests
Target.create "RunTests" <| fun _ ->
Yarn.exec "test" id
// --------------------------------------------------------------------------------------
// Generate Paket load scripts
Target.create "LoadScripts" <| fun _ ->
let frameworks =
__SOURCE_DIRECTORY__ @@ "bin"
|> Directory.EnumerateDirectories
|> Seq.map (fun d ->
Directory.EnumerateDirectories d
|> Seq.map (fun f -> DirectoryInfo(f).Name)
|> List.ofSeq)
|> List.ofSeq
|> List.reduce List.append
|> List.distinct
|> List.reduce (fun acc elem -> sprintf "%s --framework %s" elem acc)
|> function
| e when e.Length > 0 ->
Some (sprintf "--framework %s" e)
| _ -> None
let arguments =
[Some("generate-load-scripts"); frameworks]
|> List.choose id
|> List.reduce (fun acc elem -> sprintf "%s %s" acc elem)
arguments
|> CreateProcess.fromRawCommandLine ((__SOURCE_DIRECTORY__ @@ ".paket") @@ "paket.exe")
|> CreateProcess.withTimeout (TimeSpan.MaxValue)
|> CreateProcess.ensureExitCodeWithMessage "Failed to generate paket load scripts."
|> Proc.run
|> ignore
// --------------------------------------------------------------------------------------
// Update package.json version & name
Target.create "PackageJson" <| fun _ ->
let setValues (current: Json.JsonPackage) =
{ current with
Name = Str.toKebabCase project |> Some
Version = release.NugetVersion |> Some
Description = summary |> Some
Homepage = repo |> Some
Repository =
{ Json.RepositoryValue.Type = "git" |> Some
Json.RepositoryValue.Url = repo |> Some
Json.RepositoryValue.Directory = None }
|> Some
Bugs =
{ Json.BugsValue.Url =
@"https://github.com/Shmew/Feliz.UseBridge/issues/new/choose" |> Some } |> Some
License = "MIT" |> Some
Author = author |> Some
Private = true |> Some }
Json.setJsonPkg setValues
Target.create "Start" <| fun _ ->
Yarn.exec "start" id
Target.create "PublishPages" <| fun _ ->
Yarn.exec "publish-docs" id
// --------------------------------------------------------------------------------------
// Build and release NuGet targets
Target.create "NuGet" <| fun _ ->
Paket.pack(fun p ->
{ p with
OutputPath = bin
Version = release.NugetVersion
ReleaseNotes = Fake.Core.String.toLines release.Notes
ProjectUrl = repo
MinimumFromLockFile = true
IncludeReferencedProjects = true })
Target.create "NuGetPublish" <| fun _ ->
Paket.push(fun p ->
{ p with
ApiKey =
match getEnvFromAllOrNone "NUGET_KEY" with
| Some key -> key
| None -> failwith "The NuGet API key must be set in a NUGET_KEY environment variable"
WorkingDir = bin })
// --------------------------------------------------------------------------------------
// Release Scripts
let gitPush msg =
Git.Staging.stageAll ""
Git.Commit.exec "" msg
Git.Branches.push ""
Target.create "GitPush" <| fun p ->
p.Context.Arguments
|> List.choose (fun s ->
match s.StartsWith("--Msg=") with
| true -> Some(s.Substring 6)
| false -> None)
|> List.tryHead
|> function
| Some(s) -> s
| None -> (sprintf "Bump version to %s" release.NugetVersion)
|> gitPush
Target.create "GitTag" <| fun _ ->
Git.Branches.tag "" release.NugetVersion
Git.Branches.pushTag "" "origin" release.NugetVersion
Target.create "PublishDocs" <| fun _ ->
gitPush "Publishing docs"
// --------------------------------------------------------------------------------------
// Run all targets by default. Invoke 'build -t <Target>' to override
Target.create "All" ignore
Target.create "Dev" ignore
Target.create "Release" ignore
Target.create "Publish" ignore
"Clean"
==> "AssemblyInfo"
==> "Restore"
==> "PackageJson"
==> "YarnInstall"
==> "Build"
==> "RebuildSass"
==> "PostBuildClean"
==> "CopyBinaries"
//"Build" ==> "RunTests"
"Build"
==> "PostBuildClean"
==> "PublishDotNet"
==> "PostPublishClean"
==> "CopyBinaries"
"Restore" ==> "Lint"
"Lint"
?=> "Build"
//?=> "RunTests"
?=> "CleanDocs"
"Restore" ==> "LoadScripts"
"All"
==> "GitPush"
?=> "GitTag"
"All" <== ["Lint"; (*"RunTests";*) "CopyBinaries" ]
"CleanDocs"
==> "CopyDocFiles"
==> "PrepDocs"
"All"
==> "NuGet"
==> "NuGetPublish"
"PrepDocs"
==> "PublishPages"
==> "PublishDocs"
"All"
==> "PrepDocs"
"All"
==> "PrepDocs"
==> "Start"
"All" ==> "PublishPages"
"ConfigDebug" ?=> "Clean"
"ConfigRelease" ?=> "Clean"
"Dev" <== ["All"; "ConfigDebug"; "Start"]
"Release" <== ["All"; "NuGet"; "ConfigRelease"]
"Publish" <== ["Release"; "ConfigRelease"; "NuGetPublish"; "PublishDocs"; "GitTag"; "GitPush" ]
Target.runOrDefaultWithArguments "Dev"

25
build.proj Normal file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<RepoRootDir>$([System.IO.Path]::GetFullPath("$(MSBuildThisFileDirectory)"))</RepoRootDir>
<BuildDependsOn>
</BuildDependsOn>
<CoreBuildDependsOn>
</CoreBuildDependsOn>
</PropertyGroup>
<Target Name="Build">
<Exec IgnoreStandardErrorWarningFormat="true" Command="fake build" WorkingDirectory="$(RepoRootDir)" />
</Target>
<Target Name="Pack">
</Target>
<Target Name="Test">
</Target>
<Target Name="VSTest" DependsOnTargets="Test" />
<Import Project=".paket\Paket.Restore.targets" />
</Project>

15
build.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# to properly set Travis permissions: https://stackoverflow.com/questions/33820638/travis-yml-gradlew-permission-denied
# git update-index --chmod=+x fake.sh
# git commit -m "permission access for travis"
set -eu
set -o pipefail
dotnet restore build.proj
if [ ! -f build.fsx ]; then
fake run init.fsx
fi
fake build $@

203
demo/Client/App.fs Normal file
View File

@@ -0,0 +1,203 @@
namespace SignalRApp
module App =
open Elmish
open Fable.Core
open Fable.SignalR
open Fable.SignalR.Elmish
open Feliz
open Feliz.UseElmish
open SignalRHub
module Elmish =
type Model =
{ Count: int
Text: string
Hub: ElmishHub<Action,Response> option }
interface System.IDisposable with
member this.Dispose () =
this.Hub |> Option.iter (fun hub -> hub.Dispose())
type Msg =
| SignalRMsg of Response
| IncrementCount
| DecrementCount
| RandomCharacter
| SayHello
| RegisterHub of ElmishHub<Action,Response>
let init =
{ Count = 0
Text = ""
Hub = None }
, Cmd.SignalR.connect RegisterHub SignalRMsg (fun hub ->
hub.withUrl(Endpoints.Root)
.withAutomaticReconnect()
.configureLogging(LogLevel.Debug))
let update msg model =
match msg with
| RegisterHub hub -> { model with Hub = Some hub }, Cmd.none
| SignalRMsg rsp ->
match rsp with
| Response.Howdy -> model, Cmd.none
| Response.RandomCharacter str ->
{ model with Text = str }, Cmd.none
| Response.NewCount i ->
{ model with Count = i }, Cmd.none
| _ -> model, Cmd.none
| IncrementCount ->
model, Cmd.SignalR.send model.Hub (Action.IncrementCount model.Count)
| DecrementCount ->
model, Cmd.SignalR.send model.Hub (Action.DecrementCount model.Count)
| RandomCharacter ->
model, Cmd.SignalR.send model.Hub Action.RandomCharacter
| SayHello ->
model, Cmd.SignalR.send model.Hub Action.SayHello
let textDisplay = React.functionComponent(fun (input: {| count: int; text: string |}) ->
Html.div [
Html.div input.count
Html.div input.text
])
let buttons = React.functionComponent(fun (input: {| dispatch: Msg -> unit |}) ->
React.fragment [
Html.button [
prop.text "Testing"
prop.onClick <| fun _ -> input.dispatch IncrementCount
]
]
)
let render = React.functionComponent(fun () ->
let state,dispatch = React.useElmish(init, update, [||])
Html.div [
prop.children [
textDisplay {| count = state.Count; text = state.Text |}
buttons {| dispatch = dispatch |}
]
])
module Hook =
let textDisplay = React.functionComponent(fun (input: {| count: int; text: string |}) ->
Html.div [
Html.div input.count
Html.div input.text
])
let buttons = React.functionComponent(fun (input: {| count: int; hub: HubRef<Action,Response> |}) ->
React.fragment [
Html.button [
prop.text "Testing"
prop.onClick <| fun _ -> input.hub.current.send (Action.IncrementCount input.count)
]
]
)
let render = React.functionComponent(fun () ->
let count,setCount = React.useState 0
let text,setText = React.useState ""
let testing,setTesting = React.useState false
let hub =
React.useSignalR<Action,Response>({
config =
fun hub ->
hub.withUrl(Endpoints.Root)
.withAutomaticReconnect()
.configureLogging(LogLevel.Debug)
onMsg =
function
| Response.Howdy -> JS.console.log("Howdy!")
| Response.NewCount i -> setCount i
| Response.RandomCharacter str -> setText str
| _ -> ()
}, [| testing :> obj |])
React.useEffect(fun () ->
if count > 5 then setTesting true
)
Html.div [
prop.children [
textDisplay {| count = count; text = text |}
buttons {| count = count; hub = hub |}
]
])
module StreamingHook =
let textDisplay = React.functionComponent(fun (input: {| count: int; text: string |}) ->
Html.div [
Html.div input.count
Html.div input.text
])
let buttons = React.functionComponent(fun (input: {| count: int; hub: HubRef<Action,Response> |}) ->
React.fragment [
Html.button [
prop.text "Stream"
prop.onClick <| fun _ ->
promise {
let stream = input.hub.current.stream Action.GetInts
stream.subscribe (
{| closed = false
next = fun (msg: Response) ->
match msg with
| Response.GetInts i ->
JS.console.log(i)
| _ -> ()
complete = fun () -> JS.console.log("Complete!")
error = fun err -> JS.console.log(err) |}
|> unbox
) |> unbox
}
|> Promise.start
]
]
)
let render = React.functionComponent(fun () ->
let count,setCount = React.useState 0
let text,setText = React.useState ""
let testing,setTesting = React.useState false
let hub =
React.useSignalR<Action,Response>({
config =
fun hub ->
hub.withUrl(Endpoints.Root)
.withAutomaticReconnect()
.configureLogging(LogLevel.Debug)
onMsg =
function
| Response.Howdy -> JS.console.log("Howdy!")
| Response.NewCount i -> setCount i
| Response.RandomCharacter str -> setText str
| _ -> ()
}, [| testing :> obj |])
React.useEffect(fun () ->
if count > 5 then setTesting true
)
Html.div [
prop.children [
textDisplay {| count = count; text = text |}
buttons {| count = count; hub = hub |}
]
])
let render = React.functionComponent(fun () ->
Html.div [
Elmish.render()
StreamingHook.render()
])
ReactDOM.render(render, Browser.Dom.document.getElementById "app")

View File

@@ -0,0 +1,22 @@
// Auto-Generated by FAKE; do not edit
namespace System
open System.Reflection
open System.Runtime.CompilerServices
[<assembly: AssemblyTitleAttribute("Client")>]
[<assembly: AssemblyProductAttribute("GASS")>]
[<assembly: AssemblyDescriptionAttribute("GEHA Alert Suppression System")>]
[<assembly: AssemblyVersionAttribute("0.0.1")>]
[<assembly: AssemblyFileVersionAttribute("0.0.1")>]
[<assembly: AssemblyConfigurationAttribute("Release")>]
[<assembly: InternalsVisibleToAttribute("Client.Tests")>]
do ()
module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "Client"
let [<Literal>] AssemblyProduct = "GASS"
let [<Literal>] AssemblyDescription = "GEHA Alert Suppression System"
let [<Literal>] AssemblyVersion = "0.0.1"
let [<Literal>] AssemblyFileVersion = "0.0.1"
let [<Literal>] AssemblyConfiguration = "Release"
let [<Literal>] InternalsVisibleTo = "Client.Tests"

20
demo/Client/Client.fsproj Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
</PropertyGroup>
<ItemGroup>
<Content Include="public\index.html" />
<None Include="scss/main.scss" />
<Compile Include="App.fs" />
<None Include="paket.references" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Fable.SignalR.Elmish\Fable.SignalR.Elmish.fsproj" />
<ProjectReference Include="..\..\src\Fable.SignalR.Feliz\Fable.SignalR.Feliz.fsproj" />
<ProjectReference Include="..\..\src\Fable.SignalR\Fable.SignalR.fsproj" />
<ProjectReference Include="..\Shared\Shared.fsproj" />
</ItemGroup>
<Import Project="..\..\.paket\Paket.Restore.targets" />
</Project>

View File

@@ -0,0 +1,15 @@
group Client
Fable.Browser.Dom
Fable.Browser.Url
Fable.Browser.WebSocket
Fable.Core
Fable.Elmish
Fable.Elmish.React
Fable.Elmish.Debugger
Fable.Elmish.HMR
Fable.React
Fable.SimpleJson
Fable.Promise
Feliz
Feliz.UseElmish
FSharp.Core

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<title>Fable.SignalR</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -0,0 +1,31 @@
html {
height: 100%;
}
body {
height: inherit;
padding: 0 !important;
}
#app {
height: inherit;
}
*::-webkit-scrollbar {
width: 6px;
}
*::-webkit-scrollbar-track {
display: none;
}
*::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #00000000;
transition: background 3s;
}
*:hover::-webkit-scrollbar-thumb {
background: rgba(3, 218, 198, 0.54);
}

36
demo/Server/App.fs Normal file
View File

@@ -0,0 +1,36 @@
namespace SignalRApp
module App =
open Fable.SignalR
open Giraffe.ResponseWriters
open FSharp.Control.Tasks.V2
open Saturn
open System
[<EntryPoint>]
let main args =
try
let app =
application {
use_streaming_signalr (
configure_streaming_signalr {
endpoint Endpoints.Root
update SignalRHub.update
stream SignalRHub.Stream.update
}
)
error_handler (fun e log -> text e.Message)
url (sprintf "http://0.0.0.0:%i/" <| Env.getPortsOrDefault 8085us)
use_router Router.appRouter
use_static (Env.clientPath args)
use_developer_exceptions
}
printfn "Working directory - %s" (System.IO.Directory.GetCurrentDirectory())
run app
0 // return an integer exit code
with e ->
let color = Console.ForegroundColor
Console.ForegroundColor <- System.ConsoleColor.Red
Console.WriteLine(e.Message)
Console.ForegroundColor <- color
1 // return an integer exit code

View File

@@ -0,0 +1,22 @@
// Auto-Generated by FAKE; do not edit
namespace System
open System.Reflection
open System.Runtime.CompilerServices
[<assembly: AssemblyTitleAttribute("Server")>]
[<assembly: AssemblyProductAttribute("GASS")>]
[<assembly: AssemblyDescriptionAttribute("GEHA Alert Suppression System")>]
[<assembly: AssemblyVersionAttribute("0.0.1")>]
[<assembly: AssemblyFileVersionAttribute("0.0.1")>]
[<assembly: AssemblyConfigurationAttribute("Release")>]
[<assembly: InternalsVisibleToAttribute("Server.Tests")>]
do ()
module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "Server"
let [<Literal>] AssemblyProduct = "GASS"
let [<Literal>] AssemblyDescription = "GEHA Alert Suppression System"
let [<Literal>] AssemblyVersion = "0.0.1"
let [<Literal>] AssemblyFileVersion = "0.0.1"
let [<Literal>] AssemblyConfiguration = "Release"
let [<Literal>] InternalsVisibleTo = "Server.Tests"

37
demo/Server/Env.fs Normal file
View File

@@ -0,0 +1,37 @@
namespace SignalRApp
module Env =
open System
open System.IO
let clientPath args =
match Array.toList args with
| clientPath :: _ when Directory.Exists clientPath -> clientPath
| _ ->
match (Path.Combine("..", "public")), (Path.Combine("..", "Client", "public")),
(Path.Combine("src", "Client", "public")) with
| path, _, _ when Directory.Exists path -> path
| _, path, _ when Directory.Exists path -> path
| _, _, path when Directory.Exists path -> path
| _ -> @"./public"
|> Path.GetFullPath
let getEnvFromAllOrNone (s: string) =
let envOpt (envVar: string) =
if envVar = "" || isNull envVar then None
else Some(envVar)
let procVar = Environment.GetEnvironmentVariable(s) |> envOpt
let userVar = Environment.GetEnvironmentVariable(s, EnvironmentVariableTarget.User) |> envOpt
let machVar = Environment.GetEnvironmentVariable(s, EnvironmentVariableTarget.Machine) |> envOpt
match procVar, userVar, machVar with
| Some(v), _, _
| _, Some(v), _
| _, _, Some(v) -> Some(v)
| _ -> None
let getPortsOrDefault defaultVal =
match getEnvFromAllOrNone "GIRAFFE_FABLE_PORT" with
| Some value -> value |> uint16
| None -> defaultVal

33
demo/Server/Router.fs Normal file
View File

@@ -0,0 +1,33 @@
namespace SignalRApp
module Router =
open Giraffe.Core
open Giraffe.ResponseWriters
open Saturn
let browser =
pipeline {
plug acceptHtml
plug fetchSession
}
let defaultView =
router {
get "/" (htmlFile "public/index.html")
get "" (redirectTo false "/")
get "/index.html" (redirectTo false "/")
get "/default.html" (redirectTo false "/")
}
let browserRouter =
router {
not_found_handler (htmlFile "public/index.html")
pipe_through browser
forward "" defaultView
}
let appRouter =
router {
forward "" browserRouter
}

21
demo/Server/Server.fsproj Normal file
View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
<ItemGroup>
<Compile Include="SignalR.fs" />
<Compile Include="Router.fs" />
<Compile Include="Env.fs" />
<Compile Include="App.fs" />
<None Include="paket.references" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Fable.SignalR.Saturn\Fable.SignalR.Saturn.fsproj" />
<ProjectReference Include="..\Shared\Shared.fsproj" />
</ItemGroup>
<Import Project="..\..\.paket\Paket.Restore.targets" />
</Project>

47
demo/Server/SignalR.fs Normal file
View File

@@ -0,0 +1,47 @@
namespace SignalRApp
module SignalRHub =
open Fable.SignalR
open FSharp.Control
open SignalRHub
let update (msg: Action) (hubContext: StreamingFableHub<Action,Action,Response,Response>) =
printfn "New Msg: %A" msg
match msg with
| Action.SayHello ->
Response.Howdy
|> hubContext.Clients.Caller.Send
| Action.IncrementCount i ->
Response.NewCount(i + 1)
|> hubContext.Clients.Caller.Send
| Action.DecrementCount i ->
Response.NewCount(i - 1)
|> hubContext.Clients.Caller.Send
| Action.RandomCharacter ->
let characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
System.Random().Next(0,characters.Length-1)
|> fun i -> characters.ToCharArray().[i]
|> string
|> Response.RandomCharacter
|> hubContext.Clients.Caller.Send
| _ -> failwith "bad"
[<RequireQualifiedAccess>]
module Stream =
open FSharp.Control
let update (msg: Action) (hubContext: StreamingFableHub<Action,Action,Response,Response>) =
printfn "New stream msg: %A" msg
match msg with
| Action.GetInts ->
asyncSeq {
for i in [ 1 .. 100 ] do
do! Async.Sleep 100
printfn "%i" i
yield Response.GetInts i
}
|> AsyncSeq.toAsyncEnum
| _ -> failwith "Invalid"

View File

@@ -0,0 +1,5 @@
group Server
FSharp.Core
FSharp.Control.AsyncSeq
Saturn
TaskBuilder.fs

View File

@@ -0,0 +1,22 @@
// Auto-Generated by FAKE; do not edit
namespace System
open System.Reflection
open System.Runtime.CompilerServices
[<assembly: AssemblyTitleAttribute("Shared")>]
[<assembly: AssemblyProductAttribute("GASS")>]
[<assembly: AssemblyDescriptionAttribute("GEHA Alert Suppression System")>]
[<assembly: AssemblyVersionAttribute("0.0.1")>]
[<assembly: AssemblyFileVersionAttribute("0.0.1")>]
[<assembly: AssemblyConfigurationAttribute("Release")>]
[<assembly: InternalsVisibleToAttribute("Shared.Tests")>]
do ()
module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "Shared"
let [<Literal>] AssemblyProduct = "GASS"
let [<Literal>] AssemblyDescription = "GEHA Alert Suppression System"
let [<Literal>] AssemblyVersion = "0.0.1"
let [<Literal>] AssemblyFileVersion = "0.0.1"
let [<Literal>] AssemblyConfiguration = "Release"
let [<Literal>] InternalsVisibleTo = "Shared.Tests"

33
demo/Shared/Shared.fs Normal file
View File

@@ -0,0 +1,33 @@
namespace SignalRApp
module SignalRHub =
[<RequireQualifiedAccess>]
type Action =
| IncrementCount of int
| DecrementCount of int
| RandomCharacter
| SayHello
| GetInts
[<RequireQualifiedAccess>]
type Response =
| Howdy
| NewCount of int
| RandomCharacter of string
| GetInts of int
module Stream =
[<RequireQualifiedAccess>]
type Action =
| GenInts
[<RequireQualifiedAccess>]
type Response =
| GetInts of int
module Endpoints =
let port = 8080us
let baseUrl = sprintf "http://localhost:%i" port
let [<Literal>] Root = "/SignalR"

12
demo/Shared/Shared.fsproj Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
</PropertyGroup>
<ItemGroup>
<Compile Include="Shared.fs" />
<None Include="paket.references" />
</ItemGroup>
<Import Project="..\..\.paket\Paket.Restore.targets" />
</Project>

View File

@@ -0,0 +1,2 @@
group Shared
FSharp.Core

23
docs/README.md Normal file
View File

@@ -0,0 +1,23 @@
# Fable.Jester [![Nuget](https://img.shields.io/nuget/v/Fable.Jester.svg?maxAge=0&colorB=brightgreen&label=Fable.Jester)](https://www.nuget.org/packages/Fable.Jester) [![Nuget](https://img.shields.io/nuget/v/Fable.ReactTestingLibrary.svg?maxAge=0&colorB=brightgreen&label=Fable.ReactTestingLibrary)](https://www.nuget.org/packages/Fable.ReactTestingLibrary) [![Nuget](https://img.shields.io/nuget/v/Fable.FastCheck.svg?maxAge=0&colorB=brightgreen&label=Fable.FastCheck)](https://www.nuget.org/packages/Fable.FastCheck) [![Nuget](https://img.shields.io/nuget/v/Fable.FastCheck.Jest.svg?maxAge=0&colorB=brightgreen&label=Fable.FastCheck.Jest)](https://www.nuget.org/packages/Fable.FastCheck.Jest)
Fable bindings for [jest](https://github.com/facebook/jest) and friends for delightful Fable testing:
* [fast-check](https://github.com/dubzzz/fast-check)
* [jest-dom](https://github.com/testing-library/jest-dom)
* [react-testing-library](https://github.com/testing-library/react-testing-library)
* [user-event](https://github.com/testing-library/user-event)
A quick look:
```fsharp
Jest.describe("my tests", fun () ->
Jest.test("water is wet", fun () ->
Jest.expect("test").toBe("test")
Jest.expect("test").not.toBe("somethingElse")
Jest.expect("hi").toHaveLength(2)
Jest.expect("hi").not.toHaveLength(3)
)
Jest.test.prop("Is positive", Arbitrary.ConstrainedDefaults.integer(1,100), fun i ->
Jest.expect(i).toBeGreaterThan(0)
)
)
```

2
docs/RELEASE_NOTES.md Normal file
View File

@@ -0,0 +1,2 @@
### 0.0.1 - Wednesday, March 25, 2020
* Initial build

3
docs/acknowledgment.md Normal file
View File

@@ -0,0 +1,3 @@
# Acknowledgment
Big shout-out to [Zaid-Ajaj](https://github.com/Zaid-Ajaj) who helped me a lot with the API design and troubleshooting issues.

10
docs/contributing.md Normal file
View File

@@ -0,0 +1,10 @@
# Contributing
Any help is greatly appreciated!
Please ensure that all code matches the style of the rest of the api, and has comments to ensure a smooth IDE experience.
It is also very important that when possible to make the output code to be as "human" looking as possible so that troubleshooting
failing tests is as easy as possible.
I also ask that any new functionality added has matching tests to go with them so that we know it works as expected, as well as giving
examples for others to work from.

192
docs/creating-tests.md Normal file
View File

@@ -0,0 +1,192 @@
# Creating Tests
For the most part creating tests is the same as with .NET
applications, except in this case it must be run via node.js.
You will want to create a library project (I usually put mine
in tests/projectName.Tests/) You then will want to create your
tests with a specific name synax of `YourFileName.test.fs`,
this allows Jest to pick up all your tests easily without telling
it which tests it needs to run and where, everything within a
directory will be searched to find all `.test.js` files.
Almost every aspect of `Fable.Jester` and `Fable.ReactTestingLibrary`
have tests written for them, should you need the reference.
## Splitter Config
This is the main caveat when building your jest project. You will
want to make sure that you include a `splitter.config.js` file and
configure it in two main ways:
There are two main things to note here for both types:
* `allFiles: true` __Fable will not compile all of your tests
if this is not set__.
* `sourceMaps: "inline"` this enables Jest to display the FSharp
source code when a test fails rather than the transpiled Javascript.
<resolved-image source='/images/jest/sourcemap.png' />
### Without snapshot testing
When not doing snapshot testing you can forgo the config file and
use the cli, but I don't recommend it.
```js
const path = require("path");
module.exports = {
allFiles: true,
entry: path.join(__dirname, "./Fable.ReactTestingLibrary.Tests.fsproj"),
outDir: path.join(__dirname, "../../dist/tests/RTL"),
babel: {
plugins: ["@babel/plugin-transform-modules-commonjs"],
sourceMaps: "inline"
}
};
```
### With snapshot testing
Enabling snapshot testing is pretty easy, the main thing is copy
and pasting the below `onCompiled()` function. You shouldn't need
to modify anything, the node module is included in the project
and will automatically load.
```js
const path = require("path");
const testsDir = path.join(__dirname, "../../dist/tests");
module.exports = {
allFiles: true,
entry: path.join(__dirname, "./Fable.Jester.Tests.fsproj"),
outDir: testsDir,
babel: {
plugins: [
"@babel/plugin-transform-modules-commonjs"
],
sourceMaps: "inline"
},
onCompiled() {
const fs = require('fs')
const findSnapshotLoader = () => {
const jesterDir =
fs
.readdirSync(testsDir)
.sort()
.reverse()
.find(item => { return item.startsWith("Fable.Jester") })
return require(path.join(testsDir, jesterDir, "SnapshotLoader"))
}
findSnapshotLoader().copySnaps(__dirname, this.outDir)
}
};
```
## Test Structure
It is important to note that due to how Fable handles
namespaces, __you cannot use namespaces for your tests__.
Each file you will write your describe blocks (they are
optional, but it's recommended) and then place your tests
within them. That's all that's required to start testing!
Here is an example:
```fsharp
module Tests
open Fable.Jester
[<RequireQualifiedAccess>]
module Async =
let map f computation =
async {
let! res = computation
return f res
}
let myPromise = promise { return 1 + 1 }
let myAsync = async { return 1 + 1 }
Jest.describe("can run basic tests", (fun () ->
Jest.test("running a test", (fun () ->
Jest.expect(1+1).toEqual(2)
))
Jest.test("running a promise test", (fun () ->
Jest.expect(myPromise).resolves.toEqual(2)
))
Jest.test("running a promise test", promise {
do! Jest.expect(myPromise).resolves.toEqual(2)
do! Jest.expect(myPromise |> Promise.map ((+) 1)).resolves.toEqual(3)
})
Jest.test("running an async test", (fun () ->
Jest.expect(myAsync).toEqual(2)
))
Jest.test("running an async test", async {
do! Jest.expect(myAsync).toEqual(2)
do! Jest.expect(myAsync |> Async.map ((+) 1)).toEqual(3)
})
))
Jest.describe("how to run a test like test.each", (fun () ->
Jest.test("same functionality as test.each", (fun () ->
for (input, output) in [|(1, 2);(2, 3);(3, 4)|] do
Jest.expect(input + 1).toEqual(output)
))
))
Jest.describe("how to run a describe like describe.each", (fun () ->
let myTestCases = [
(1, 1, 2)
(1, 2, 3)
(2, 1, 3)
]
for (a, b, expected) in myTestCases do
Jest.test(sprintf "%i + %i returns %i" a b expected, (fun () ->
Jest.expect(a + b).toBe(expected)
))
))
Jest.describe("tests with the skip modifier don't get run", (fun () ->
Jest.test.skip("adds", (fun () ->
Jest.expect(true).toEqual(false)
))
Jest.test("this should execute", (fun () ->
Jest.expect(true).toEqual(true)
))
))
Jest.describe("todo tests give us our todo", (fun () ->
Jest.test.todo "Do this!"
))
Jest.describe.skip("these shouldn't run", (fun () ->
Jest.test("this shouldn't run", (fun () ->
Jest.expect(true).toEqual(false)
))
Jest.test("this shouldn't run either", (fun () ->
Jest.expect(true).toEqual(false)
))
))
```
## Snapshot Testing
Doing snapshots is very simple, you will run a test
that calls `toMatchSnapshot()`. If the file does not
exist, it will get generated in your project directory.
From that point forward when you run your tests it will
confirm the DOM structure matches that of your snapshot.
See [jest documentation] for more information.
[jest documentation]: https://jestjs.io/docs/en/snapshot-testing

10
docs/fast-check/README.md Normal file
View File

@@ -0,0 +1,10 @@
# Fable.FastCheck
Fable.FastCheck are bindings to use [fast-check]
and to property test Fable applications.
This library has significant modifications to the original API
to better support F# and Fable use cases, as well as extend
the functionality.
[fast-check]: https://github.com/dubzzz/fast-check

View File

@@ -0,0 +1,552 @@
# Arbitrary
Arbitrary is the interface that holds all the details needed
to generate the values for your tests:
```fsharp
type Random =
/// Clone the random number generator
clone: unit -> Random
/// Generate an integer having `bits` random bits.
next: bits: int -> int
/// Generate a random boolean.
nextBoolean: unit -> bool
/// Generate a random integer (32 bits).
nextInt: unit -> int
/// Generate a random integer between min (included) and max (included).
nextInt: min: int * max: int -> int
/// Generate a random any between min (included) and max (included).
nextBigInt: min: bigint * max: bigint -> bigint
/// Generate a random floating point number between 0.0 (included) and 1.0 (excluded).
nextDouble: unit -> float
/// A Shrinkable<'T> holds an internal value of type `'T`
/// and can shrink it to smaller values.
type Shrinkable<'T> =
value_ : 'T
shrink: unit -> seq<Shrinkable<'T>>
/// State storing the result of hasCloneMethod.
///
/// If true the value will be cloned each time it gets accessed.
hasToBeCloned : bool
/// Safe value of the shrinkable.
///
/// Depending on hasToBeCloned it will either be value_ or a clone of it.
value : 'T
/// Create another shrinkable by mapping all values using the provided `mapper`
///
/// Both the original value and the shrunk ones are impacted.
map: mapper: ('T -> 'U) -> Shrinkable<'U>
/// Create another shrinkable by filtering its shrunk values against a predicate.
///
/// Return true to keep the element, false otherwise.
filter: predicate: ('T -> bool) -> Shrinkable<'T>
type Arbitrary<'T> =
/// Generate a value of type `'T` along with its shrink method
/// based on the provided random number generator.
generate: mrng: Random -> Shrinkable<'T>
/// Create another arbitrary by filtering values against a predicate.
///
/// Return true to keep the element, false otherwise.
filter: predicate: ('T -> bool) -> Arbitrary<'T>
/// Create another arbitrary by mapping all produced values using the provided mapper function.
map: mapper: ('T -> 'U) -> Arbitrary<'U>
/// Create another arbitrary by mapping a value from a base Arbirary using the fmapper function.
[<Emit("$0.chain($1)")>]
bind: fmapper: ('T -> Arbitrary<'U>) -> Arbitrary<'U>
/// Create another Arbitrary with no shrink values.
noShrink: unit -> Arbitrary<'T>
/// Create another Arbitrary having bias - by default returns itself.
withBias: freq: float -> Arbitrary<'T>
/// Create another Arbitrary that cannot be biased.
noBias: unit -> Arbitrary<'T>
type ArbitraryWithShrink<'T> =
inherit Arbitrary<'T>
/// Produce a stream of shrinks of value.
shrink: value: 'T * ?shrunkOnce: bool -> seq<'T>
/// Build the Shrinkable associated to value.
shrinkableFor: value: 'T * ?shrunkOnce: bool -> Shrinkable<'T>
```
The functions outlined below are located in the `Arbitrary` module.
## apply
Signature:
```fsharp
(arbF: Arbitrary<'T -> 'U>) (arb: Arbitrary<'T>) -> Arbitrary<'U>
```
## asyncCommands
<Note>See [Model Testing](/model-testing) for usage.</Note>
Sequence of IAsyncCommand to be executed by asyncModelRun.
This implementation comes with a shrinker adapted for commands.
It should shrink more efficiently than a normal sequence of IAsyncCommand.
Signature:
```fsharp
(commandArbs: Arbitrary<IAsyncCommand<'Model,'Real>> list)
-> Arbitrary<seq<IAsyncCommand<'Model,'Real>>>
```
## asyncCommandsOfMax
<Note>See [Model Testing](/model-testing) for usage.</Note>
Sequence of IAsyncCommand to be executed by asyncModelRun.
This implementation comes with a shrinker adapted for commands.
It should shrink more efficiently than a normal sequence of IAsyncCommand.
Signature:
```fsharp
(maxCommands: int) (commandArbs: Arbitrary<IAsyncCommand<'Model,'Real>> list)
-> Arbitrary<seq<IAsyncCommand<'Model,'Real>>>
```
## asyncCommandsOfSettings
<Note>See [Model Testing](/model-testing) for usage.</Note>
Sequence of IAsyncCommand to be executed by asyncModelRun.
This implementation comes with a shrinker adapted for commands.
It should shrink more efficiently than a normal sequence of IAsyncCommand.
Signature:
```fsharp
(settings: ICommandConstraintProperty list)
(commandArbs: Arbitrary<IAsyncCommand<'Model,'Real>> list)
-> Arbitrary<seq<IAsyncCommand<'Model,'Real>>>
```
## auto
Attempts to auto generate arbitraries for a given type.
This is mostly intended for complex types that
would be very cumbersome to write an Arbitrary for.
All types generated from this will use the default
Arbitrary for each primitive.
Classes are currently [not supported](https://github.com/fable-compiler/Fable/issues/2027).
Signature:
```fsharp
unit -> Arbitrary<'T>
```
Usage:
```fsharp
type MyDU =
| Empty
| SingleValue of int
| Record of RecordTest
| Function of (int -> string)
Arbitrary.auto<MyDU>()
```
## bind
Signature:
```fsharp
(f: 'A -> Arbitrary<'B>) (arb: Arbitrary<'A>) -> Arbitrary<'B>
```
## bind2
Signature:
```fsharp
(f: 'A -> 'B -> Arbitrary<'C>) (a: Arbitrary<'A>) (b: Arbitrary<'B>) -> Arbitrary<'B>
```
## choose
Applies the given function to the arbitrary.
Returns an arbitrary comprised of the results
x for each generated value where the function
returns Some(x).
Signature:
```fsharp
(chooser: 'T -> 'U option) (arb: Arbitrary<'T>) -> Arbitrary<'U>
```
## clonedConstant
Clones a constant, useful when generating an arbitrary from a mutable value.
Signature:
```fsharp
(value: 'T) -> Arbitrary<'T>
```
## constant
Creates an arbitrary that returns a constant value.
Signature:
```fsharp
(value: 'T) -> Arbitrary<'T>
```
## commands
<Note>See [Model Testing](/model-testing) for usage.</Note>
Sequence of Command to be executed by modelRun.
This implementation comes with a shrinker adapted for commands.
It should shrink more efficiently than a normal sequence of Commands.
Signature:
```fsharp
(commandArbs: Arbitrary<ICommand<'Model,'Real>> list)
-> Arbitrary<seq<ICommand<'Model,'Real>>>
```
## commandsOfMax
<Note>See [Model Testing](/model-testing) for usage.</Note>
Sequence of Command to be executed by modelRun.
This implementation comes with a shrinker adapted for commands.
It should shrink more efficiently than a normal sequence of Commands.
Signature:
```fsharp
(maxCommands: int) (commandArbs: Arbitrary<ICommand<'Model,'Real>> list)
-> Arbitrary<seq<ICommand<'Model,'Real>>>
```
## commandsOfSettings
<Note>See [Model Testing](/model-testing) for usage.</Note>
Sequence of Command to be executed by modelRun.
This implementation comes with a shrinker adapted for commands.
It should shrink more efficiently than a normal sequence of Commands.
Signature:
```fsharp
(settings: ICommandConstraintProperty list) (commandArbs: Arbitrary<ICommand<'Model,'Real>> list)
-> Arbitrary<seq<ICommand<'Model,'Real>>>
```
## elements
Build an arbitrary that randomly generates one of the values in the given non-empty seq.
Signature:
```fsharp
(xs: 'T seq) -> Arbitrary<'T>
```
## elmish
<Note>See [Elmish Model Testing](/elmish-model-testing) for usage.</Note>
Creates an arbitrary of elmish commands to use with runModel.
Signature:
```fsharp
// Uses auto<'Msg>() to generate Msgs
(init: 'Model * Elmish.Cmd<'Msg>,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: ('Msg -> 'Model -> 'Model -> unit))
-> Arbitrary<Model<'Model,'Msg> *
Model<'Model,'Msg> *
seq<ICommand<Model<'Model,'Msg>,Model<'Model,'Msg>>>>
(init: 'Model * Elmish.Cmd<'Msg>,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: ('Msg -> 'Model -> 'Model -> unit),
msgs: Arbitrary<'Msg list>)
-> Arbitrary<Model<'Model,'Msg> *
Model<'Model,'Msg> *
seq<ICommand<Model<'Model,'Msg>,Model<'Model,'Msg>>>>
// Uses auto<'Msg>() to generate Msgs
(init: 'Model,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: ('Msg -> 'Model -> 'Model -> unit))
-> Arbitrary<Model<'Model,'Msg> *
Model<'Model,'Msg> *
seq<ICommand<Model<'Model,'Msg>,Model<'Model,'Msg>>>>
(init: 'Model,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: ('Msg -> 'Model -> 'Model -> unit),
msgs: Arbitrary<'Msg list>)
-> Arbitrary<Model<'Model,'Msg> *
Model<'Model,'Msg> *
seq<ICommand<Model<'Model,'Msg>,Model<'Model,'Msg>>>>
// Uses auto<'Msg>() to generate Msgs
(init: 'Model,
update: 'Msg -> 'Model -> 'Model,
asserter: ('Msg -> 'Model -> 'Model -> unit))
-> Arbitrary<Model<'Model,'Msg> *
Model<'Model,'Msg> *
seq<ICommand<Model<'Model,'Msg>,Model<'Model,'Msg>>>>
(init: 'Model,
update: 'Msg -> 'Model -> 'Model,
asserter: ('Msg -> 'Model -> 'Model -> unit),
msgs: Arbitrary<'Msg list>)
-> Arbitrary<Model<'Model,'Msg> *
Model<'Model,'Msg> *
seq<ICommand<Model<'Model,'Msg>,Model<'Model,'Msg>>>>
```
## filter
Create another arbitrary by filtering values against a predicate.
Return true to keep the element, false otherwise.
Signature:
```fsharp
(a: Arbitrary<'T>) -> Arbitrary<'T>
```
## func
Creates an arbitrary function that returns the given arbitrary value.
Signature:
```fsharp
(arb: Arbitrary<'TOut>) -> Arbitrary<'T -> 'TOut>
```
## infiniteStream
Produce an infinite stream of values.
<Note type="warning">Requires Object.assign.</Note>
Signature:
```fsharp
(arb: Arbitrary<'T>) -> Arbitrary<seq<'T>>
```
## map
Includes map through to map6.
Signature:
```fsharp
(f: 'A -> 'B) (a: Arbitrary<'A>) -> Arbitrary<'B>
(f: 'A -> 'B -> 'C) (a: Arbitrary<'A>) (b: Arbitrary<'B>) -> Arbitrary<'C>
(f: 'A -> 'B -> 'C -> 'D) (a: Arbitrary<'A>) (b: Arbitrary<'B>) (c: Arbitrary<'C>) -> Arbitrary<'D>
...
```
## mixedCase
Randomly switch the case of characters generated by `Arbitrary<string>` (upper/lower).
Signature:
```fsharp
(stringArb: Arbitrary<string>) -> Arbitrary<string>
```
## mixedCaseWithToggle
Randomly switch the case of characters generated by `Arbitrary<string>` (upper/lower).
Signature:
```fsharp
(toggleCase: bool) (stringArb: Arbitrary<string>) -> Arbitrary<string>
```
## option
Generates an option of a given arbitrary.
Signature:
```fsharp
(arb: Arbitrary<'T>) -> Arbitrary<'T option>
```
## optionOfFreq
Generates an option of a given arbitrary.
The probability of None is `1. / freq`.
Signature:
```fsharp
(freq: float) (arb: Arbitrary<'T>) -> Arbitrary<'T option>
```
## promiseCommands
<Note>See [Model Testing](/model-testing) for usage.</Note>
Sequence of IPromiseCommand to be executed by promiseModelRun.
This implementation comes with a shrinker adapted for commands.
It should shrink more efficiently than a normal sequence of IPromiseCommand.
Signature:
```fsharp
(commandArbs: Arbitrary<IPromiseCommand<'Model,'Real>> list)
-> Arbitrary<seq<IPromiseCommand<'Model,'Real>>>
```
## promiseCommandsOfMax
<Note>See [Model Testing](/model-testing) for usage.</Note>
Sequence of IPromiseCommand to be executed by promiseModelRun.
This implementation comes with a shrinker adapted for commands.
It should shrink more efficiently than a normal sequence of IPromiseCommand.
Signature:
```fsharp
(maxCommands: int) (commandArbs: Arbitrary<IPromiseCommand<'Model,'Real>> list)
-> Arbitrary<seq<IPromiseCommand<'Model,'Real>>>
```
## promiseCommandsOfSettings
<Note>See [Model Testing](/model-testing) for usage.</Note>
Sequence of IPromiseCommand to be executed by promiseModelRun.
This implementation comes with a shrinker adapted for commands.
It should shrink more efficiently than a normal sequence of IPromiseCommand.
Signature:
```fsharp
(settings: ICommandConstraintProperty list)
(commandArbs: Arbitrary<IPromiseCommand<'Model,'Real>> list)
-> Arbitrary<seq<IPromiseCommand<'Model,'Real>>>
```
## record
<Note type="warning">This does not produce F# records.</Note>
Records following the `recordModel` schema.
Signature:
```fsharp
(recordModel: Map<string,obj>) -> Arbitrary<obj>
```
## recordWithDeletedKeys
Signature:
```fsharp
(recordModel: Map<string,obj>) -> Arbitrary<obj>
```
## result
Generates a result of the given arbitraries.
Signature:
```fsharp
(ok: Arbitrary<'Success>) (err: Arbitrary<'Failure>) -> Arbitrary<Result<'Success,'Failure>>
```
## resultOfFreq
Generates a result of the given arbitraries.
The probability of Error is `1. / freq`.
Signature:
```fsharp
(freq: float) (ok: Arbitrary<'Success>) (err: Arbitrary<'Failure>)
-> Arbitrary<Result<'Success,'Failure>>
```
## stringOf
Creates a string arbitrary using the characters produced by a char arbitrary.
Signature:
```fsharp
(charArb: Arbitrary<char>) -> Arbitrary<string>
```
## stringOfMaxSize
Creates a string arbitrary using the characters produced by a char arbitrary.
Signature:
```fsharp
(maxLength: int) (charArb: Arbitrary<char>) -> Arbitrary<string>
```
## stringOfSize
Creates a string arbitrary using the characters produced by a char arbitrary.
Signature:
```fsharp
(minLength: int) (maxLength: int) (charArb: Arbitrary<char>) -> Arbitrary<string>
```
## unzip
Includes unzip through to unzip6.
Signature:
```fsharp
(a: Arbitrary<'A * 'B>) -> Arbitrary<'A> * Arbitrary<'B>
(a: Arbitrary<'A * 'B * 'C>) -> Arbitrary<'A> * Arbitrary<'B> * Arbitrary<'C>
...
```
## zip
Includes zip through to zip6.
Signature:
```fsharp
(a: Arbitrary<'A>) (b: Arbitrary<'B>) -> Arbitrary<'A * 'B>
(a: Arbitrary<'A>) (b: Arbitrary<'B>) (c: Arbitrary<'C>) -> Arbitrary<'A * 'B * 'C>
```

View File

@@ -0,0 +1,107 @@
# Array
These are functions to help compose Arbitrary arrays.
This is accessed via:
```fsharp
Arbitrary.Array
```
## ofLength
Signature:
```fsharp
(size: int) (arb: Arbitrary<'T>) -> Arbitrary<'T []>
```
## ofRange
Signature:
```fsharp
(min: int) (max: int) (arb: Arbitrary<'T>) -> Arbitrary<'T []>
```
## piles
Creates an arbitrary of a collection of a given length
such that all elements have the given sum.
Signature:
```fsharp
(length: int) (sum: int) -> Arbitrary<'T []>
```
## sequence
Signature:
```fsharp
(arbs: Arbitrary<'T> []) -> Arbitrary<'T []>
```
## shuffle
Creates an arbitrary of a collection that is shuffled.
Signature:
```fsharp
(xs: 'T []) -> Arbitrary<'T []>
```
## shuffledSub
Creates an arbitrary that is shuffled and a sub-section of the given collection.
Signature:
```fsharp
(originalArray: 'T []) -> Arbitrary<'T []>
```
## shuffledSubOfSize
Creates an arbitrary that is shuffled and a sub-section of the given collection.
Signature:
```fsharp
(minLength: int) (maxLength: int) (xs: 'T []) -> Arbitrary<'T []>
```
## sub
Creates an arbitrary that is a sub-section of the given collection.
Signature:
```fsharp
(xs: 'T []) -> Arbitrary<'T []>
```
## subOfSize
Signature:
```fsharp
(minLength: int) (maxLength: int) (xs: 'T []) -> Arbitrary<'T []>
```
## traverse
Signature:
```fsharp
(f: 'T -> Arbitrary<'U>) (arbs: Arbitrary<'T> []) -> Arbitrary<'U []>
```
## twoDimOf
Creates a array of arrays arbitrary from a given arbitrary.
Signature:
```fsharp
(arb: Arbitrary<'T>) -> Arbitrary<'T [][]>
```
## twoDimOfDim
Creates an array of arrays arbitrary from a given arbitrary.
Signature:
```fsharp
(rows: int) (cols: int) (arb: Arbitrary<'T>) -> Arbitrary<'T [][]>
```

View File

@@ -0,0 +1,35 @@
# Arbitrary Computation Expression
<Note>If you're unfamiliar with computation expressions
I recommend you read this [blog series]</Note>
A computation expression for constructing
and composing Arbitraries is exposed via
`arbitrary`
The following methods are defined in the builder:
- Bind
- Combine
- Delay
- For
- Return
- ReturnFrom
- Run
- TryFinally
- TryWith
- Using
- While
- Zero
Which can be used like this:
```fsharp
let intTupleArb =
arbitrary {
let! i = Arbitrary.Defaults.integer
let! i2 = Arbitrary.Defaults.integer
return i,i2
}
```
[blog series]:https://fsharpforfunandprofit.com/series/computation-expressions.html

View File

@@ -0,0 +1,518 @@
# Constrained Defaults
Provides functions to easily customize behavior of many of the [Defaults](#Defaults).
This is accessible via:
```fsharp
Arbitrary.ConstrainedDefaults
```
Parameters that take a `I_ConstraintProperty` list are accessible via:
```fsharp
Constraints
```
```fsharp
type Command =
/// Maximum number of commands to execute on the model.
maxCommands: (value: int)
/// Disable replaying of the model test.
disableReplayLog: (value: bool)
/// Set the reply path.
replayPath: (value: string)
type Date =
/// Minimum value for date (inclusive).
min: (value: DateTime)
/// Maximum value for date (inclusive).
max: (value: DateTime)
type Obj<'T> =
/// Maximal depth allowed.
maxDepth: (value: int)
/// Maximal number of keys.
maxKeys: (value: int)
/// Arbitrary for keys.
///
/// Default for `key` is: `Arbitrary.Defaults.string`
key: (value: Arbitrary<string>)
/// Arbitrary for values.
values: (value: Arbitrary<'T> [])
/// Arbitrary for values.
values: (value: Arbitrary<'T> list)
/// Arbitrary for values.
values: (value: Arbitrary<'T> seq)
/// Arbitrary for values.
values: (value: ResizeArray<Arbitrary<'T>>)
/// Also generate boxed versions of values.
withBoxedValues: (value: bool)
/// Also generate Set.
withSet: (value: bool)
/// Also generate Map.
withMap: (value: bool)
/// Also generate string representations of object instances.
withObjectString: (value: bool)
/// Also generate object with null prototype.
withNullPrototype: (value: bool)
module Uuid =
type VersionNumber =
N1
N2
N3
N4
N5
type WebAuthority =
/// Enable IPv4 in host.
withIPv4: (value: bool)
/// Enable extended IPv4 format.
withIPv4Extended: (value: bool)
/// Enable IPv6 in host.
withIPv6: (value: bool)
/// Enable port suffix.
withPort: (value: bool)
/// Enable user information prefix.
withUserInfo: (value: bool)
type WebUrl =
/// Enforce specific schemes, eg.: http, https.
validSchemes: (value: string list)
/// Settings: for webAuthority.
authoritySettings: (properties: IWebAuthorityConstraintProperty list)
/// Enable query parameters in the generated url.
withQueryParameters: (value: bool)
/// Enable fragments in the generated url.
withFragments: (value: bool)
```
## anything
Any type of values.
Signature:
```fsharp
(constraints: IObjConstraintProperty list)
```
Returns:
```fsharp
Arbitrary<obj>
```
## asciiString
An [ascii](#ascii) string.
Signature:
```fsharp
(maxLength: int)
(minLength: int, maxLength: int)
```
Returns:
```fsharp
Arbitrary<string>
```
## asyncScheduler
Creates a scheduler with a wrapped act function.
See [scheduler](/scheduler) for more details.
Signature:
```fsharp
(act: ((unit -> Async<unit>) -> Async<unit>))
```
Returns:
```fsharp
Arbitrary<AsyncScheduler>
```
## base64String
A base64 string will always have a length multiple of 4 (padded with =)
Signature:
```fsharp
(maxLength: int)
(minLength: int, maxLength: int)
```
Returns:
```fsharp
Arbitrary<string>
```
## bigInt
All possible bigint between min (included) and max (included).
Signature:
```fsharp
(min: bigint, max: bigint)
```
Returns:
```fsharp
Arbitrary<bigint>
```
## bigIntN
All possible bigint between -2^(n-1) (included) and 2^(n-1)-1 (included).
Signature:
```fsharp
(min: bigint, max: bigint)
```
Returns:
```fsharp
Arbitrary<bigint>
```
## bigUint
All possible bigint between 0 (included) and max (included).
Signature:
```fsharp
(max: bigint)
```
Returns:
```fsharp
Arbitrary<bigint>
```
## bigUintN
All possible bigint between 0 (included) and 2^n -1 (included).
Signature:
```fsharp
(n: int)
```
Returns:
```fsharp
Arbitrary<bigint>
```
## date
Any DateTime value.
Signature:
```fsharp
(constraints: IDateConstraintProperty list)
```
Returns:
```fsharp
Arbitrary<DateTime>
```
## double
Floating point numbers between 0.0 (included) and max (excluded) - accuracy of `max / 2**53`.
and
Floating point numbers between min (included) and max (excluded) - accuracy of `(max - min) / 2**53`.
Signature:
```fsharp
(max: float)
(min: float, max: float)
```
Returns:
```fsharp
Arbitrary<float>
```
## float
Floating point numbers between 0.0 (included) and max (excluded) - accuracy of `max / 2**24`.
and
Floating point numbers between min (included) and max (excluded) - accuracy of `(max - min) / 2**24`.
Signature:
```fsharp
(max: float)
(min: float, max: float)
```
Returns:
```fsharp
Arbitrary<float>
```
## fullUnicodeString
A [fullUnicode](#fullUnicode) string.
Signature:
```fsharp
(maxLength: int)
(minLength: int, maxLength: int)
```
Returns:
```fsharp
Arbitrary<string>
```
## hexaString
A [hexa](#hexa) string.
Signature:
```fsharp
(maxLength: int)
(minLength: int, maxLength: int)
```
Returns:
```fsharp
Arbitrary<string>
```
## integer
Any integer value.
Signature:
```fsharp
(max: int)
(min: int, max: int)
```
Returns:
```fsharp
Arbitrary<int>
```
## json
JSON strings with a maximal depth.
Signature:
```fsharp
(maxDepth: int)
```
Returns:
```fsharp
Arbitrary<string>
```
## jsonObject
JSON compliant values with a maximal depth.
Signature:
```fsharp
(maxDepth: int)
```
Returns:
```fsharp
Arbitrary<obj>
```
## lorem
Lorem ipsum string of words with maximal number of words.
and
Lorem ipsum string of words or sentences with maximal number of words or sentences.
Signature:
```fsharp
(maxWordsCount: float)
(maxWordsCount: float, sentencesMode: bool)
```
Returns:
```fsharp
Arbitrary<string>
```
## object
Any object.
Signature:
```fsharp
(constraints: IObjConstraintProperty list)
```
Returns:
```fsharp
Arbitrary<obj>
```
## promiseScheduler
Creates a scheduler with a wrapped act function.
See [scheduler](/scheduler) for more details.
Signature:
```fsharp
(act: ((unit -> JS.Promise<unit>) -> JS.Promise<unit>))
```
Returns:
```fsharp
Arbitrary<PromiseScheduler>
```
## string
Any string value.
Signature:
```fsharp
(maxLength: int)
(minLength: int, maxLength: int)
```
Returns:
```fsharp
Arbitrary<string>
```
## string16bits
Any 16-bit string.
Signature:
```fsharp
(maxLength: int)
(minLength: int, maxLength: int)
```
Returns:
```fsharp
Arbitrary<string>
```
## unicodeJson
JSON strings with unicode support and a maximal depth.
Signature:
```fsharp
(maxDepth: int)
```
Returns:
```fsharp
Arbitrary<string>
```
## unicodeJsonObject
JSON compliant values with unicode support and a maximal depth.
Signature:
```fsharp
(maxDepth: int)
```
Returns:
```fsharp
Arbitrary<obj>
```
## unicodeString
Any unicode compliant string.
Signature:
```fsharp
(maxLength: int)
(minLength: int, maxLength: int)
```
Returns:
```fsharp
Arbitrary<string>
```
## uuidV
UUID of a given version (in v1 to v5).
According to [RFC 4122](https://tools.ietf.org/html/rfc4122).
No mixed case, only lower case digits (0-9a-f).
Signature:
```fsharp
(versionNumber: IUuidVersionConstraintProperty)
```
Returns:
```fsharp
Arbitrary<string>
```
## webAuthority
According to [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt)
`authority = [ userinfo "@" ] host [ ":" port ]`
Signature:
```fsharp
(constraints: IWebAuthorityConstraintProperty list)
```
Returns:
```fsharp
Arbitrary<string>
```
## webUrl
According to [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt)
and [WHATWG URL Standard](https://url.spec.whatwg.org/).
Signature:
```fsharp
(constraints: IWebUrlConstraintProperty list)
```
Returns:
```fsharp
Arbitrary<string>
```

View File

@@ -0,0 +1,596 @@
# Defaults
The Arbitrary module has a collection of default Arbitraries
for you to use or compose into new ones.
This is accessible via:
```fsharp
Arbitrary.Defaults
```
## anything
Any type of values.
Returns:
```fsharp
Arbitrary<obj>
```
## ascii
Single ascii characters - char code between 0x00 (included) and 0x7f (included).
Returns:
```fsharp
Arbitrary<char>
```
## asciiString
An [ascii](#ascii) string.
Returns:
```fsharp
Arbitrary<string>
```
## asyncScheduler
A scheduler instance.
See [scheduler](/scheduler) for more details.
Returns:
```fsharp
Arbitrary<AsyncScheduler>
```
## base64
Single base64 characters - A-Z, a-z, 0-9, + or /
Returns:
```fsharp
Arbitrary<char>
```
## base64String
A base64 string will always have a length multiple of 4 (padded with =).
Returns:
```fsharp
Arbitrary<string>
```
## bigInt
Uniformly distributed bigint values.
Returns:
```fsharp
Arbitrary<bigint>
```
## bigUint
Uniformly distributed bigint positive values.
Returns:
```fsharp
Arbitrary<bigint>
```
## boolean
true or false.
Returns:
```fsharp
Arbitrary<bool>
```
## byte
Any byte value.
Returns:
```fsharp
Arbitrary<byte>
```
## char
Single printable ascii characters - char code between
0x20 (included) and 0x7e (included)
Returns:
```fsharp
Arbitrary<char>
```
## char16bits
Single characters - all values in 0x0000-0xffff can be generated
<Note type="warning">Some generated characters might appear invalid regarding UCS-2 and UTF-16 encoding.</Note>
Indeed values within 0xd800 and 0xdfff constitute surrogate pair
characters and are illegal without their paired character.
Returns:
```fsharp
Arbitrary<char>
```
## compareBooleanFunc
A comparison boolean function returns:
- true whenever a < b
- false otherwise (ie. a = b or a > b)
Returns:
```fsharp
Arbitrary<obj -> obj -> bool>
```
## compareFunc
A comparison function returns:
- Negative value whenever a < b.
- Positive value whenever a > b
- Zero whenever a and b are equivalent
Comparison functions are transitive: `a < b and b < c => a < c`
They also satisfy: `a < b <=> b > a` and `a = b <=> b = a`
Returns:
```fsharp
Arbitrary<obj -> obj -> int>
```
## dateTime
Any DateTime value.
Returns:
```fsharp
Arbitrary<DateTime>
```
## dateTimeOffset
Any DateTimeOffset value.
Returns:
```fsharp
Arbitrary<DateTimeOffset>
```
## domain
Having an extension with at least two lowercase characters.
According to [RFC 1034](https://www.ietf.org/rfc/rfc1034.txt),
[RFC 1123](https://www.ietf.org/rfc/rfc1123.txt) and
[WHATWG URL Standard](https://url.spec.whatwg.org/).
Returns:
```fsharp
Arbitrary<string>
```
## double
Floating point numbers between 0.0 (included) and 1.0 (excluded) - accuracy of `1 / 2**53`.
Returns:
```fsharp
Arbitrary<float>
```
## emailAddress
According to [RFC 5322](https://www.ietf.org/rfc/rfc5322.txt)
Returns:
```fsharp
Arbitrary<string>
```
## exn
A System.Exception with random `message` string.
Returns:
```fsharp
Arbitrary<exn>
```
## float
Floating point numbers between 0.0 (included) and
1.0 (excluded) - accuracy of `1 / 2**24`.
Returns:
```fsharp
Arbitrary<float>
```
## float32
Any float32 value.
Returns:
```fsharp
Arbitrary<float32>
```
## fullUnicode
Single unicode characters - any of the code points defined in the unicode standard.
<Note type="warning">Generated values can have a length greater than 1, so the generated
type is a string.</Note>
Returns:
```fsharp
Arbitrary<string>
```
## fullUnicodeString
A [fullUnicode](#fullUnicode) string.
Returns:
```fsharp
Arbitrary<string>
```
## guid
Any guid value.
Returns:
```fsharp
Arbitrary<Guid>
```
## hexa
Single hexadecimal characters - 0-9 or a-f
Returns:
```fsharp
Arbitrary<char>
```
## hexaString
A [hexa](#hexa) string.
Returns:
```fsharp
Arbitrary<string>
```
## int16
Any int16 value.
Returns:
```fsharp
Arbitrary<int16>
```
## integer
Any integer value.
Returns:
```fsharp
Arbitrary<int>
```
## int64
Integers between Number.MIN_SAFE_INTEGER (included)
and Number.MAX_SAFE_INTEGER (included).
Returns:
```fsharp
Arbitrary<int64>
```
## ipV4
Valid IP v4.
Following [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2).
Returns:
```fsharp
Arbitrary<string>
```
## ipV4Extended
Valid IP v4 according to WhatWG.
Following the WhatWG [specification for web-browsers](https://url.spec.whatwg.org/)
There is no equivalent for IP v6 according to the [IP v6 parser](https://url.spec.whatwg.org/#concept-ipv6-parser)
Returns:
```fsharp
Arbitrary<string>
```
## ipV6
Valid IP v6.
Following [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2)
Returns:
```fsharp
Arbitrary<string>
```
## json
JSON compliant string.
Returns:
```fsharp
Arbitrary<string>
```
## jsonObject
JSON compliant values.
Returns:
```fsharp
Arbitrary<obj>
```
## lorem
Lorem ipsum strings of words.
Returns:
```fsharp
Arbitrary<string>
```
## maxSafeInteger
Integers between Number.MIN_SAFE_INTEGER
(included) and Number.MAX_SAFE_INTEGER (included).
Returns:
```fsharp
Arbitrary<int64>
```
## maxSafeNat
positive integers between 0 (included) and
Number.MAX_SAFE_INTEGER (included).
Returns:
```fsharp
Arbitrary<int64>
```
## object
Any object.
Returns:
```fsharp
Arbitrary<obj>
```
## promiseScheduler
A scheduler instance.
See [scheduler](/scheduler) for more details.
Returns:
```fsharp
Arbitrary<PromiseScheduler>
```
## regex
Any valid `Regex`.
Returns:
```fsharp
Arbitrary<Regex>
```
## sbyte
Any sbyte value.
Returns:
```fsharp
Arbitrary<sbyte>
```
## string
Any string value.
Returns:
```fsharp
Arbitrary<string>
```
## string16bits
Any 16-bit string.
Returns:
```fsharp
Arbitrary<string>
```
## timeSpan
Any TimeSpan.
Returns:
```fsharp
Arbitrary<TimeSpan>
```
## unicode
Single unicode characters defined in the BMP plan -
char code between 0x0000 (included) and 0xffff
(included) and without the range 0xd800 to 0xdfff
(surrogate pair characters).
Returns:
```fsharp
Arbitrary<char>
```
## unicodeJson
JSON strings with unicode support.
Returns:
```fsharp
Arbitrary<string>
```
## unicodeJsonObject
JSON compliant values with unicode support.
Returns:
```fsharp
Arbitrary<obj>
```
## unicodeString
Any unicode compliant string.
Returns:
```fsharp
Arbitrary<string>
```
## uint16
Any uint16 value.
Returns:
```fsharp
Arbitrary<uint16>
```
## uint32
Any uint32 value.
Returns:
```fsharp
Arbitrary<uint32>
```
## uint64
Some unint64 values.
<Note>Due to number limitations this is just an alias and cast from maxSafeNat.</Note>
Returns:
```fsharp
Arbitrary<uint64>
```
## uuid
UUID from v1 to v5.
According to [RFC 4122](https://tools.ietf.org/html/rfc4122)
No mixed case, only lower case digits (0-9a-f).
Returns:
```fsharp
Arbitrary<string>
```
## webAuthority
According to [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt)
`authority = [ userinfo "@" ] host [ ":" port ]`
Returns:
```fsharp
Arbitrary<string>
```
## webFragments
Fragments of an URI (web included).
According to [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt)
eg.: In the url `https://domain/plop?page=1#hello=1&world=2`,
`?hello=1&world=2` are query parameters.
Returns:
```fsharp
Arbitrary<string>
```
## webQueryParameters
Query parameters of an URI (web included).
According to [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt)
eg.: In the url `https://domain/plop/?hello=1&world=2`,
`?hello=1&world=2` are query parameters.
Returns:
```fsharp
Arbitrary<string>
```
## webSegment
Internal segment of an URI (web included).
According to [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt)
eg.: In the url `https://github.com/dubzzz/fast-check/`,
`dubzzz` and `fast-check` are segments.
Returns:
```fsharp
Arbitrary<string>
```
## webUrl
According to [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt)
and [WHATWG URL Standard](https://url.spec.whatwg.org/).
Returns:
```fsharp
Arbitrary<string>
```

View File

@@ -0,0 +1,107 @@
# List
These are functions to help compose Arbitrary lists.
This is accessed via:
```fsharp
Arbitrary.List
```
## ofLength
Signature:
```fsharp
(size: int) (arb: Arbitrary<'T>) -> Arbitrary<'T list>
```
## ofRange
Signature:
```fsharp
(min: int) (max: int) (arb: Arbitrary<'T>) -> Arbitrary<'T list>
```
## piles
Creates an arbitrary of a collection of a given length
such that all elements have the given sum.
Signature:
```fsharp
(length: int) (sum: int) -> Arbitrary<'T list>
```
## sequence
Signature:
```fsharp
(arbs: Arbitrary<'T> list) -> Arbitrary<'T list>
```
## shuffle
Creates an arbitrary of a collection that is shuffled.
Signature:
```fsharp
(xs: 'T list) -> Arbitrary<'T list>
```
## shuffledSub
Creates an arbitrary that is shuffled and a sub-section of the given collection.
Signature:
```fsharp
(originalArray: 'T list) -> Arbitrary<'T list>
```
## shuffledSubOfSize
Creates an arbitrary that is shuffled and a sub-section of the given collection.
Signature:
```fsharp
(minLength: int) (maxLength: int) (xs: 'T list) -> Arbitrary<'T list>
```
## sub
Creates an arbitrary that is a sub-section of the given collection.
Signature:
```fsharp
(xs: 'T list) -> Arbitrary<'T list>
```
## subOfSize
Signature:
```fsharp
(minLength: int) (maxLength: int) (xs: 'T list) -> Arbitrary<'T list>
```
## traverse
Signature:
```fsharp
(f: 'T -> Arbitrary<'U>) (arbs: Arbitrary<'T> list) -> Arbitrary<'U list>
```
## twoDimOf
Creates a list of lists arbitrary from a given arbitrary.
Signature:
```fsharp
(arb: Arbitrary<'T>) -> Arbitrary<'T list list>
```
## twoDimOfDim
Creates an list of lists arbitrary from a given arbitrary.
Signature:
```fsharp
(rows: int) (cols: int) (arb: Arbitrary<'T>) -> Arbitrary<'T list list>
```

View File

@@ -0,0 +1,23 @@
# Map
These are functions to help compose Arbitrary Maps.
This is accessed via:
```fsharp
Arbitrary.Map
```
## ofLength
Signature:
```fsharp
(length: int) (key: Arbitrary<'Key>) (value: Arbitrary<'Value>) -> Arbitrary<Map<'Key,'Value>>
```
## ofRange
Signature:
```fsharp
(min: int) (max: int) (key: Arbitrary<'Key>) (value: Arbitrary<'Value>)
-> Arbitrary<Map<'Key,'Value>>
```

View File

@@ -0,0 +1,108 @@
# ResizeArray
These are functions to help compose Arbitrary ResizeArrays.
This is accessed via:
```fsharp
Arbitrary.ResizeArray
```
## ofLength
Signature:
```fsharp
(size: int) (arb: Arbitrary<'T>) -> Arbitrary<ResizeArray<<'T>>
```
## ofRange
Signature:
```fsharp
(min: int) (max: int) (arb: Arbitrary<'T>) -> Arbitrary<ResizeArray<'T>>
```
## piles
Creates an arbitrary of a collection of a given length
such that all elements have the given sum.
Signature:
```fsharp
(length: int) (sum: int) -> Arbitrary<ResizeArray<'T>>
```
## sequence
Signature:
```fsharp
(arbs: ResizeArray<Arbitrary<'T>>) -> Arbitrary<ResizeArray<'T>>
```
## shuffle
Creates an arbitrary of a collection that is shuffled.
Signature:
```fsharp
(xs: ResizeArray<'T>) -> Arbitrary<ResizeArray<<'T>>
```
## shuffledSub
Creates an arbitrary that is shuffled and a sub-section of the given collection.
Signature:
```fsharp
(originalArray: ResizeArray<'T>) -> Arbitrary<ResizeArray<'T>>
```
## shuffledSubOfSize
Creates an arbitrary that is shuffled and a sub-section of the given collection.
Signature:
```fsharp
(minLength: int) (maxLength: int) (xs: ResizeArray<'T>) -> Arbitrary<ResizeArray<'T>>
```
## sub
Creates an arbitrary that is a sub-section of the given collection.
Signature:
```fsharp
(xs: ResizeArray<'T>) -> Arbitrary<ResizeArray<'T>>
```
## subOfSize
Signature:
```fsharp
(minLength: int) (maxLength: int) (xs: ResizeArray<'T>) -> Arbitrary<ResizeArray<'T>>
```
## traverse
Signature:
```fsharp
(f: 'T -> Arbitrary<'U>) (arbs: ResizeArray<Arbitrary<'T>>) -> Arbitrary<ResizeArray<'U>>
```
## twoDimOf
Creates a ResizeArray of ResizeArrays arbitrary from a given arbitrary.
Signature:
```fsharp
(arb: Arbitrary<'T>) -> Arbitrary<ResizeArray<ResizeArray<'T>>>
```
## twoDimOfDim
Creates an ResizeArray of ResizeArrays arbitrary from a given arbitrary.
Signature:
```fsharp
(rows: int) (cols: int) (arb: Arbitrary<'T>) -> Arbitrary<ResizeArray<ResizeArray<'T>>>
```

View File

@@ -0,0 +1,107 @@
# Seq
These are functions to help compose Arbitrary sequences.
This is accessed via:
```fsharp
Arbitrary.Seq
```
## ofLength
Signature:
```fsharp
(size: int) (arb: Arbitrary<'T>) -> Arbitrary<'T seq>
```
## ofRange
Signature:
```fsharp
(min: int) (max: int) (arb: Arbitrary<'T>) -> Arbitrary<'T seq>
```
## piles
Creates an arbitrary of a collection of a given length
such that all elements have the given sum.
Signature:
```fsharp
(length: int) (sum: int) -> Arbitrary<'T seq>
```
## sequence
Signature:
```fsharp
(arbs: Arbitrary<'T> seq) -> Arbitrary<'T seq>
```
## shuffle
Creates an arbitrary of a collection that is shuffled.
Signature:
```fsharp
(xs: 'T seq) -> Arbitrary<'T seq>
```
## shuffledSub
Creates an arbitrary that is shuffled and a sub-section of the given collection.
Signature:
```fsharp
(originalArray: 'T seq) -> Arbitrary<'T seq>
```
## shuffledSubOfSize
Creates an arbitrary that is shuffled and a sub-section of the given collection.
Signature:
```fsharp
(minLength: int) (maxLength: int) (xs: 'T seq) -> Arbitrary<'T seq>
```
## sub
Creates an arbitrary that is a sub-section of the given collection.
Signature:
```fsharp
(xs: 'T seq) -> Arbitrary<'T seq>
```
## subOfSize
Signature:
```fsharp
(minLength: int) (maxLength: int) (xs: 'T seq) -> Arbitrary<'T seq>
```
## traverse
Signature:
```fsharp
(f: 'T -> Arbitrary<'U>) (arbs: Arbitrary<'T> seq) -> Arbitrary<'U seq>
```
## twoDimOf
Creates a seq of seqs arbitrary from a given arbitrary.
Signature:
```fsharp
(arb: Arbitrary<'T>) -> Arbitrary<'T seq seq>
```
## twoDimOfDim
Creates an seq of seqs arbitrary from a given arbitrary.
Signature:
```fsharp
(rows: int) (cols: int) (arb: Arbitrary<'T>) -> Arbitrary<'T seq seq>
```

View File

@@ -0,0 +1,22 @@
# Set
These are functions to help compose Arbitrary Sets.
This is accessed via:
```fsharp
Arbitrary.Set
```
## ofLength
Signature:
```fsharp
(length: int) (arb: Arbitrary<'T>) -> Arbitrary<Set<'T>>
```
## ofRange
Signature:
```fsharp
(min: int) (max: int) (arb: Arbitrary<'T>) -> Arbitrary<Set<'T>>
```

View File

@@ -0,0 +1,91 @@
# Elmish Model Testing
This library has some helpers to make a using
model-based testing compatible with [Elmish] by
testing your update function and resulting model.
<Note>If you want to learn more about Elmish
I highly recommend you have a gander at
[Zaid-Ajaj]'s [Book] on the subject.</Note>
To start you need to have the usual suspects:
```fsharp
type Model = { Count: int }
let init () =
{ Count = 0 }, Cmd.none
type Msg =
| Decrement
| Increment
let update msg (model: Model) =
match msg with
| Decrement -> { model with Count = model.Count - 1 }, Cmd.none
| Increment -> { model with Count = model.Count + 1 }, Cmd.none
```
Then we define the assertions that will be made
against the model during the test:
```fsharp
let asserter msg oldModel newModel =
match msg with
| Decrement -> Jest.expect(oldModel.Count).toBeGreaterThan(newModel.Count)
| Increment -> Jest.expect(oldModel.Count).toBeLessThan(newModel.Count)
```
Then it's time to run our tests:
```fsharp
// If you're using Fable.FastCheck.Jest
Jest.test.elmish("test my elmish", init(), update, asserter)
Jest.test("test my elmish", fun () ->
FastCheck.assert'(
FastCheck.property(
Arbitrary.elmish(init, update, asserter),
fun (model, real, cmds) ->
FastCheck.modelRun(model, real, cmds)
)
)
)
```
Wait, what is this `test.elmish`/`Arbitrary.elmish` doing?
Great question! If you do not provide your own Arbitrary
like this:
```fsharp
let msgs = Arbitrary.constant [
Decrement
Increment
]
// If you're using Fable.FastCheck.Jest
Jest.test.elmish("test my elmish", init(), update, asserter, msgs)
Jest.test("test my elmish", fun () ->
FastCheck.assert'(
FastCheck.property(
Arbitrary.elmish(init, update, asserter, msgs),
fun (model, real, cmds) ->
FastCheck.modelRun(model, real, cmds)
)
)
)
```
Then [Arbitrary.auto] is called on the `'Msg` type provided.
This function will attempt to generate an arbitrary for the
entire discriminated union. It supports nested structures,
functions, etc. As long as you don't need custom behavior
this is a way to quickly get tests up and running without
constructing some potentially very large arbitraries.
[Arbitrary.auto]:/fast-check/arbitrary/arbitrary#auto
[Book]:https://zaid-ajaj.github.io/the-elmish-book
[Elmish]:https://github.com/elmish/elmish
[Zaid-Ajaj]:https://github.com/Zaid-Ajaj

View File

@@ -0,0 +1,658 @@
# FastCheck
FastCheck is the entry point to using most of the
functionality of Fable.FastCheck.
## FastCheck Options
Many of the functions below have overloads for `IFastCheckOptionsProperty list`,
these are built using the `property` type:
```fsharp
type FastCheckOptions =
/// Stop run on failure
///
/// It makes the run stop at the first encountered failure without shrinking.
///
/// When used in complement to `seed` and `path`,
/// it replays only the minimal counterexample.
endOnFailure: (value: bool)
/// Custom values added at the beginning of generated ones
///
/// It enables users to come with examples they want to test at every run
examples: (value: 'T list)
/// Interrupt test execution after a given time limit: disabled by default
///
/// NOTE: Relies on `Date.now()`.
///
/// NOTE:
/// Useful to avoid having too long running processes in your CI.
///
/// Replay capability (see seed, path) can still be used if needed.
///
/// WARNING:
/// If the test got interrupted before any failure occured
/// and before it reached the requested number of runs specified by numRuns
/// it will be marked as success. Except if markInterruptAsFailure as been
/// set to `true`
interruptAfterTimeLimit: (value: int)
/// Logger (see statistics): `console.log` by default
logger: (value: string -> unit)
/// Mark interrupted runs as failed runs: disabled by default
markInterruptAsFailure: (value: bool)
/// Maximal number of skipped values per run
///
/// Skipped is considered globally, so this value is used to compute maxSkips =
/// maxSkipsPerRun * numRuns.
///
/// Runner will consider a run to have failed if it skipped maxSkips+1 times
/// before having generated numRuns valid entries.
///
/// See pre for more details on pre-conditions
maxSkipsPerRun: (value: int)
/// Number of runs before success: 100 by default
numRuns: (value: int)
/// Way to replay a failing property directly with the counterexample.
///
/// It can be fed with the counterexamplePath returned by the failing test
/// (requires `seed` too).
path: (value: string)
/// Initial seed of the generator: `Date.now()` by default
///
/// It can be forced to replay a failed run.
///
/// In theory, seeds are supposed to be 32 bits integers.
///
/// In case of double value, the seed will be rescaled into a
/// valid 32 bits integer (eg.: values between 0 and 1 will be evenly spread
/// into the range of possible seeds).
seed: (value: float)
/// Skip all runs after a given time limit: disabled by default
///
/// NOTE: Relies on `Date.now()`.
///
/// NOTE:
/// Useful to stop too long shrinking processes.
/// Replay capability (see seed, path) can resume the shrinking.
///
/// WARNING:
/// It skips runs. Thus test might be marked as failed.
/// Indeed, it might not reached the requested number of successful runs.
skipAllAfterTimeLimit: (value: int)
/// Maximum time in milliseconds for the predicate to answer: disabled by
/// default
///
/// WARNING: Only works for async code (see asyncProperty), will not
/// interrupt a synchronous code.
timeout: (value: int)
/// Force the use of unbiased arbitraries: biased by default
unbiased: (value: bool)
module FastCheckOptions =
/// Random generator is the core element behind the generation of random values
/// - changing it might directly impact the quality and performances of the
/// generation of random values.
///
/// Default: xorshift128plus
type randomType =
congruential
congruential32
mersenne
xorshift128plus
xoroshiro128plus
/// Set verbosity level.
type verbose =
none
verbose
veryVerbose
```
## assert'
Run the property, throw in case of failure.
It can be called directly from describe/it blocks of Mocha and Jest.
Signature:
```fsharp
(prop: AsyncProperty<'T>, ?fastCheckOptions: IFastCheckOptionsProperty list) -> JS.Promise<unit>
(prop: Property<'T>, ?fastCheckOptions: IFastCheckOptionsProperty list) -> unit
```
You can use this like so:
```fsharp
FastCheck.assert'(FastCheck.property(Arbitrary.Defaults.integer, fun i ->
Jest.expect(add i).toEqual(i + 1))
)
```
## asyncCheck
Run the property, does not throw contrary to [assert](#assert).
Signature:
```fsharp
type ExecutionStatus =
| Success = 0
| Skipped = -1
| Failure = 1
/// Summary of the execution process.
type ExecutionTree<'T> =
/// Status of the property.
status: ExecutionStatus
/// Generated value.
value: 'T
/// Values derived from this value.
children: ExecutionTree<'T> list
type VerbosityLevel =
| None = 0
| Verbose = 1
| VeryVerbose = 2
/// Post-run details produced by check.
///
/// A failing property can easily detected by checking the `failed` flag of this structure.
type RunDetails<'T> =
/// If the test failed.
failed: bool
/// If the execution was interrupted.
interrupted: bool
/// Number of runs.
///
/// - In case of failed property: Number of runs up to the first failure
/// (including the failure run).
///
/// - Otherwise: Number of successful executions.
numRuns: float
/// Number of skipped entries due to failed pre-condition.
///
/// As `numRuns` it only takes into account the skipped values that occured before the
/// first failure.
numSkips: float
/// Number of shrinks required to get to the minimal failing case (aka counterexample).
numShrinks: float
/// Seed that have been used by the run.
///
/// It can be forced in assert', check, sample and statistics using parameters.
seed: float
/// In case of failure: the counterexample contains the minimal failing case
/// (first failure after shrinking).
counterexample: 'T option
/// In case of failure: it contains the reason of the failure.
error: string option
/// In case of failure: path to the counterexample.
///
/// For replay purposes, it can be forced in assert', check, sample and statistics using
/// parameters.
counterexamplePath: string option
/// List all failures that have occurred during the run.
///
/// You must enable verbose with at least Verbosity.Verbose in Parameters
/// in order to have values present.
failures: 'T list
/// Execution summary of the run.
///
/// Traces the origin of each value encountered during the test and its execution status.
///
/// Can help to diagnose shrinking issues.
///
/// You must enable verbose with at least Verbosity.Verbose in Parameters
/// in order to have values in it:
///
/// - Verbose: Only failures.
///
/// - VeryVerbose: Failures, Successes and Skipped.
executionSummary: ExecutionTree<'T> list
/// Verbosity level required by the user.
verbose: VerbosityLevel
(prop: AsyncProperty<'T>, ?fastCheckOptions: IFastCheckOptionsProperty list)
-> Async<RunDetails<'T>>
```
You can use this like so:
```fsharp
FastCheck.asyncCheck(FastCheck.asyncProperty(Arbitrary.Defaults.integer, fun i ->
async { return add i = i + 1 }
) |> Async.map(fun res -> Jest.expect(res.failed).toEqual(false))
```
## asyncModelRun
Run asynchronous commands over a Model and the Real system.
Throw in case of inconsistency.
Signature:
```fsharp
(initialModel: 'Model, real: 'Real, commandIter: seq<IAsyncCommand<'Model,'Real>>)
-> JS.Promise<unit>
(initialModel: 'Model, real: 'Real, commandIter: IAsyncCommandSeq<'Model,'Real>)
-> JS.Promise<unit>
```
See [Model Testing](/fast-check/model-testing) for usage.
## asyncProperty
Instantiate a new AsyncProperty.
Properties are the type used to make FastCheck assertions via [assert'](#assert) and [check](#check).
Signature:
```fsharp
(arb0: Arbitrary<'T0>, predicate: ('T0 -> Async<bool>)) -> AsyncProperty<'T0>
(arb0: Arbitrary<'T0>, predicate: ('T0 -> Async<unit>)) -> AsyncProperty<'T0>
(arb0: Arbitrary<'T0>, arb1: Arbitrary<'T1>, predicate: ('T0 -> 'T1 -> Async<bool>))
-> AsyncProperty<'T0 * 'T1>
(arb0: Arbitrary<'T0>, arb1: Arbitrary<'T1>, predicate: ('T0 -> 'T1 -> Async<unit>))
-> AsyncProperty<'T0 & 'T1>
...
```
You can use this like so:
```fsharp
FastCheck.property(Arbitrary.Defaults.integer, fun i ->
promise { return Jest.expect(add i).toEqual(i + 1) }
)
```
## check
Run the property, does not throw contrary to [assert](#assert).
Signature:
```fsharp
type ExecutionStatus =
| Success = 0
| Skipped = -1
| Failure = 1
/// Summary of the execution process.
type ExecutionTree<'T> =
/// Status of the property.
status: ExecutionStatus
/// Generated value.
value: 'T
/// Values derived from this value.
children: ExecutionTree<'T> list
type VerbosityLevel =
| None = 0
| Verbose = 1
| VeryVerbose = 2
/// Post-run details produced by check.
///
/// A failing property can easily detected by checking the `failed` flag of this structure.
type RunDetails<'T> =
/// If the test failed.
failed: bool
/// If the execution was interrupted.
interrupted: bool
/// Number of runs.
///
/// - In case of failed property: Number of runs up to the first failure
/// (including the failure run).
///
/// - Otherwise: Number of successful executions.
numRuns: float
/// Number of skipped entries due to failed pre-condition.
///
/// As `numRuns` it only takes into account the skipped values that occured before the
/// first failure.
numSkips: float
/// Number of shrinks required to get to the minimal failing case (aka counterexample).
numShrinks: float
/// Seed that have been used by the run.
///
/// It can be forced in assert', check, sample and statistics using parameters.
seed: float
/// In case of failure: the counterexample contains the minimal failing case
/// (first failure after shrinking).
counterexample: 'T option
/// In case of failure: it contains the reason of the failure.
error: string option
/// In case of failure: path to the counterexample.
///
/// For replay purposes, it can be forced in assert', check, sample and statistics using
/// parameters.
counterexamplePath: string option
/// List all failures that have occurred during the run.
///
/// You must enable verbose with at least Verbosity.Verbose in Parameters
/// in order to have values present.
failures: 'T list
/// Execution summary of the run.
///
/// Traces the origin of each value encountered during the test and its execution status.
///
/// Can help to diagnose shrinking issues.
///
/// You must enable verbose with at least Verbosity.Verbose in Parameters
/// in order to have values in it:
///
/// - Verbose: Only failures.
///
/// - VeryVerbose: Failures, Successes and Skipped.
executionSummary: ExecutionTree<'T> list
/// Verbosity level required by the user.
verbose: VerbosityLevel
(prop: Property<'T>, ?fastCheckOptions: IFastCheckOptionsProperty list) -> RunDetails<'T>
```
You can use this like so:
```fsharp
FastCheck.check(FastCheck.property(Arbitrary.Defaults.integer, fun i ->
add i = i + 1
) |> fun res -> Jest.expect(res.failed).toEqual(false)
```
## modelRun
Fires a DOM event.
Signature:
```fsharp
(initialModel: 'Model, real: 'Real, commandIter: seq<ICommand<'Model,'Real>>) -> unit
(initialModel: 'Model, real: 'Real, commandIter: ICommandSeq<'Model,'Real>) -> unit
```
See [Model Testing](/fast-check/model-testing) for usage.
## promiseCheck
Run the property, does not throw contrary to [assert](#assert).
Signature:
```fsharp
type ExecutionStatus =
| Success = 0
| Skipped = -1
| Failure = 1
/// Summary of the execution process.
type ExecutionTree<'T> =
/// Status of the property.
status: ExecutionStatus
/// Generated value.
value: 'T
/// Values derived from this value.
children: ExecutionTree<'T> list
type VerbosityLevel =
| None = 0
| Verbose = 1
| VeryVerbose = 2
/// Post-run details produced by check.
///
/// A failing property can easily detected by checking the `failed` flag of this structure.
type RunDetails<'T> =
/// If the test failed.
failed: bool
/// If the execution was interrupted.
interrupted: bool
/// Number of runs.
///
/// - In case of failed property: Number of runs up to the first failure
/// (including the failure run).
///
/// - Otherwise: Number of successful executions.
numRuns: float
/// Number of skipped entries due to failed pre-condition.
///
/// As `numRuns` it only takes into account the skipped values that occured before the
/// first failure.
numSkips: float
/// Number of shrinks required to get to the minimal failing case (aka counterexample).
numShrinks: float
/// Seed that have been used by the run.
///
/// It can be forced in assert', check, sample and statistics using parameters.
seed: float
/// In case of failure: the counterexample contains the minimal failing case
/// (first failure after shrinking).
counterexample: 'T option
/// In case of failure: it contains the reason of the failure.
error: string option
/// In case of failure: path to the counterexample.
///
/// For replay purposes, it can be forced in assert', check, sample and statistics using
/// parameters.
counterexamplePath: string option
/// List all failures that have occurred during the run.
///
/// You must enable verbose with at least Verbosity.Verbose in Parameters
/// in order to have values present.
failures: 'T list
/// Execution summary of the run.
///
/// Traces the origin of each value encountered during the test and its execution status.
///
/// Can help to diagnose shrinking issues.
///
/// You must enable verbose with at least Verbosity.Verbose in Parameters
/// in order to have values in it:
///
/// - Verbose: Only failures.
///
/// - VeryVerbose: Failures, Successes and Skipped.
executionSummary: ExecutionTree<'T> list
/// Verbosity level required by the user.
verbose: VerbosityLevel
(prop: AsyncProperty<'T>, ?fastCheckOptions: IFastCheckOptionsProperty list)
-> Async<RunDetails<'T>>
```
You can use this like so:
```fsharp
FastCheck.promiseCheck(FastCheck.promiseProperty(Arbitrary.Defaults.integer, fun i ->
promise { return add i = i + 1 }
) |> Promise.map(fun res -> Jest.expect(res.failed).toEqual(false))
```
## promiseProperty
Instantiate a new AsyncProperty.
Properties are the type used to make FastCheck assertions via [assert'](#assert) and [check](#check).
Signature:
```fsharp
(arb0: Arbitrary<'T0>, predicate: ('T0 -> JS.Promise<bool>)) -> AsyncProperty<'T0>
(arb0: Arbitrary<'T0>, predicate: ('T0 -> JS.Promise<unit>)) -> AsyncProperty<'T0>
(arb0: Arbitrary<'T0>, arb1: Arbitrary<'T1>, predicate: ('T0 -> 'T1 -> JS.Promise<bool>))
-> AsyncProperty<'T0 * 'T1>
(arb0: Arbitrary<'T0>, arb1: Arbitrary<'T1>, predicate: ('T0 -> 'T1 -> JS.Promise<unit>))
-> AsyncProperty<'T0 & 'T1>
...
```
## property
Instantiate a new Property.
Properties are the type used to make FastCheck assertions via [assert'](#assert)
and [check](#check).
Signature:
```fsharp
(arb0: Arbitrary<'T0>, predicate: ('T0 ->bool)) -> Property<'T0>
(arb0: Arbitrary<'T0>, predicate: ('T0 ->unit)) -> Property<'T0>
(arb0: Arbitrary<'T0>, arb1: Arbitrary<'T1>, predicate: ('T0 -> 'T1 -> bool)) -> Property<'T0 * 'T1>
(arb0: Arbitrary<'T0>, arb1: Arbitrary<'T1>, predicate: ('T0 -> 'T1 -> unit)) -> Property<'T0 & 'T1>
...
```
You can use this like so:
```fsharp
FastCheck.property(Arbitrary.Defaults.integer, fun i ->
Jest.expect(add i).toEqual(i + 1)
)
```
## sample
Generate a list containing all the values that would have been generated during
[assert'](#assert) and [check](#check).
Signature:
```fsharp
(arb: Arbitrary<'T>) -> 'T list
(arb: Arbitrary<'T>, fastCheckOptions: IFastCheckOptionsProperty list) -> 'T list
(arb: Arbitrary<'T>, numValues: int) -> 'T list
(arb: IProperty<'T,'Return>, fastCheckOptions: IFastCheckOptionsProperty list) -> 'T list
(arb: IProperty<'T,'Return>, numValues: int) -> 'T list
```
You can use this like so:
```fsharp
FastCheck.sample Arbitrary.Defaults.integer |> List.iter (printfn "%i")
```
## scheduledModelRun
Run asynchronous and scheduled commands over a Model and the Real system.
Throw in case of inconsistency.
Signature:
```fsharp
(scheduler: AsyncScheduler,
initialModel: 'Model,
real: 'Real,
commandIter: seq<ICommand<'Model,'Real>>)
-> JS.Promise<unit>
(scheduler: AsyncScheduler,
initialModel: 'Model,
real: 'Real,
commandIter: ICommandSeq<'Model,'Real>)
-> JS.Promise<unit>
(scheduler: PromiseScheduler,
initialModel: 'Model,
real: 'Real,
commandIter: seq<ICommand<'Model,'Real>>)
-> JS.Promise<unit>
(scheduler: PromiseScheduler,
initialModel: 'Model,
real: 'Real,
commandIter: ICommandSeq<'Model,'Real>)
-> JS.Promise<unit>
```
See [Model Testing](/fast-check/model-testing) and [Scheduler](/fast-check/scheduler) for usage.
## statistics
Gather useful statistics concerning generated values.
Prints the result in `console.log` or `params.logger` (if defined).
Classifier function that can classify the generated value in zero, one, or more categories (with free labels).
Signature:
```fsharp
(arb: Arbitrary<'T>, classify: 'T -> string) -> unit
(arb: Arbitrary<'T>, classify: 'T -> string,
fastCheckOptions: IFastCheckOptionsProperty list) -> unit
(arb: Arbitrary<'T>, classify: 'T -> string, numValues: int) -> unit
(arb: Arbitrary<'T>, classify: 'T -> seq<string>) -> unit
(arb: Arbitrary<'T>, classify: 'T -> seq<string>,
fastCheckOptions: IFastCheckOptionsProperty list) -> unit
(arb: Arbitrary<'T>, classify: 'T -> seq<string>, numValues: int) -> unit
(prop: IProperty<'T,'Return>, classify: 'T -> string) -> unit
(prop: IProperty<'T,'Return>, classify: 'T -> string,
fastCheckOptions: IFastCheckOptionsProperty list) -> unit
(prop: IProperty<'T,'Return>, classify: 'T -> string, numValues: int) -> unit
(prop: IProperty<'T,'Return>, classify: 'T -> seq<string>) -> unit
(prop: IProperty<'T,'Return>, classify: 'T -> seq<string>,
fastCheckOptions: IFastCheckOptionsProperty list) -> unit
(prop: IProperty<'T,'Return>, classify: 'T -> seq<string>, numValues: int) -> unit
```
You can use this like so:
```fsharp
FastCheck.statistics(Arbitrary.Defaults.integer, id)
```
## stringify
Convert any value to its fast-check string representation.
Signature:
```fsharp
(value: 'T) -> string
```
You can use this like so:
```fsharp
FastCheck.stringify 1
```

View File

@@ -0,0 +1,511 @@
# Jest Extension
If you're using `Fable.Jest` then you can
install the `Fable.FastCheck.Jest` which
allows using `Fable.FastCheck` quite a bit
more convenient.
All of the extensions are for `Jest.test`.
## elmish
<Note>See [Elmish Model Testing](/elmish-model-testing) for usage.</Note>
Executes a model-based test using the init and
update functions of an elmish application,
performs checks based on the given message via
the paired assertion.
The assertions list is where your Jest.expect
assertion(s) should be located.
Signature:
```fsharp
(name: string,
init: 'Model * Elmish.Cmd<'Msg>,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: 'Msg -> 'Model -> 'Model -> unit,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model * Elmish.Cmd<'Msg>,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: 'Msg -> 'Model -> 'Model -> unit,
msgs: Arbitrary<'Msg list>,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: 'Msg -> 'Model -> 'Model -> unit,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: 'Msg -> 'Model -> 'Model -> unit,
msgs: Arbitrary<'Msg list>,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model,
update: 'Msg -> 'Model -> 'Model,
asserter: 'Msg -> 'Model -> 'Model -> unit,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model,
update: 'Msg -> 'Model -> 'Model,
asserter: 'Msg -> 'Model -> 'Model -> unit,
msgs: Arbitrary<'Msg list>,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
```
## elmish.only
<Note>See [Elmish Model Testing](/elmish-model-testing) for usage.</Note>
Executes only this model-based test using the init and
update functions of an elmish application,
performs checks based on the given message via
the paired assertion.
The assertions list is where your Jest.expect
assertion(s) should be located.
Signature:
```fsharp
(name: string,
init: 'Model * Elmish.Cmd<'Msg>,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: 'Msg -> 'Model -> 'Model -> unit,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model * Elmish.Cmd<'Msg>,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: 'Msg -> 'Model -> 'Model -> unit,
msgs: Arbitrary<'Msg list>,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: 'Msg -> 'Model -> 'Model -> unit,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: 'Msg -> 'Model -> 'Model -> unit,
msgs: Arbitrary<'Msg list>,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model,
update: 'Msg -> 'Model -> 'Model,
asserter: 'Msg -> 'Model -> 'Model -> unit,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model,
update: 'Msg -> 'Model -> 'Model,
asserter: 'Msg -> 'Model -> 'Model -> unit,
msgs: Arbitrary<'Msg list>,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
```
## elmish.skip
<Note>See [Elmish Model Testing](/elmish-model-testing) for usage.</Note>
Skips this model-based test using the init and
update functions of an elmish application,
performs checks based on the given message via
the paired assertion.
The assertions list is where your Jest.expect
assertion(s) should be located.
Signature:
```fsharp
(name: string,
init: 'Model * Elmish.Cmd<'Msg>,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: 'Msg -> 'Model -> 'Model -> unit,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model * Elmish.Cmd<'Msg>,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: 'Msg -> 'Model -> 'Model -> unit,
msgs: Arbitrary<'Msg list>,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: 'Msg -> 'Model -> 'Model -> unit,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model,
update: 'Msg -> 'Model -> 'Model * Elmish.Cmd<'Msg>,
asserter: 'Msg -> 'Model -> 'Model -> unit,
msgs: Arbitrary<'Msg list>,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model,
update: 'Msg -> 'Model -> 'Model,
asserter: 'Msg -> 'Model -> 'Model -> unit,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
init: 'Model,
update: 'Msg -> 'Model -> 'Model,
asserter: 'Msg -> 'Model -> 'Model -> unit,
msgs: Arbitrary<'Msg list>,
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
```
## prop
Runs a test using the provided arbitraries and predicate function.
Signature:
```fsharp
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> Async<bool>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> Async<unit>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> JS.Promise<bool>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> JS.Promise<unit>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> bool),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> unit),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> Async<bool>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> Async<unit>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> JS.Promise<bool>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> JS.Promise<unit>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> bool),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> unit),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
...
```
Usage:
```fsharp
Jest.test.prop("My arb test", Arbitrary.Defaults.integer, fun i ->
Jest.expect(i+1).toEqual(i+1)
)
```
## prop.only
Runs only this test using the provided arbitraries and predicate function.
Signature:
```fsharp
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> Async<bool>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> Async<unit>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> JS.Promise<bool>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> JS.Promise<unit>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> bool),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> unit),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> Async<bool>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> Async<unit>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> JS.Promise<bool>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> JS.Promise<unit>),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> bool),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> unit),
?fastCheckOptions: IFastCheckOptionsProperty list,
?timeout: int)
-> unit
...
```
Usage:
```fsharp
Jest.test.prop.only("My arb test", Arbitrary.Defaults.integer, fun i ->
Jest.expect(i+1).toEqual(i+1)
)
```
## prop.skip
Skips this test using the provided arbitraries and predicate function.
Signature:
```fsharp
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> Async<bool>),
?fastCheckOptions: IFastCheckOptionsProperty list)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> Async<unit>),
?fastCheckOptions: IFastCheckOptionsProperty list)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> JS.Promise<bool>),
?fastCheckOptions: IFastCheckOptionsProperty list)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> JS.Promise<unit>),
?fastCheckOptions: IFastCheckOptionsProperty list)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> bool),
?fastCheckOptions: IFastCheckOptionsProperty list)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
predicate: ('T0 -> unit),
?fastCheckOptions: IFastCheckOptionsProperty list)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> Async<bool>),
?fastCheckOptions: IFastCheckOptionsProperty list)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> Async<unit>),
?fastCheckOptions: IFastCheckOptionsProperty list)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> JS.Promise<bool>),
?fastCheckOptions: IFastCheckOptionsProperty list)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> JS.Promise<unit>),
?fastCheckOptions: IFastCheckOptionsProperty list)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> bool),
?fastCheckOptions: IFastCheckOptionsProperty list)
-> unit
(name: string,
arb0: Arbitrary<'T0>,
arb1: Arbitrary<'T1>,
predicate: ('T0 -> 'T1 -> unit),
?fastCheckOptions: IFastCheckOptionsProperty list)
-> unit
...
```
Usage:
```fsharp
Jest.test.prop.skip("My arb test", Arbitrary.Defaults.integer, fun i ->
Jest.expect(i+1).toEqual(i+1)
)
```

View File

@@ -0,0 +1,331 @@
# Model Testing
Model-based testing is a method of testing
a system by applying commands that modify
the state of the application, and verifying
that the output matches what is expected
for each action.
Performing model-based testing with fast-check
means that the commands issued to the model
are generated by an Arbitrary, allowing easier
testing of cumulative state changes.
There are a few different ways you can compose
your model-based test using the following interfaces:
```fsharp
type ICommand<'Model,'Real> =
/// Check if the model is in the right state to apply the command.
abstract check: m: 'Model -> bool
/// Receive the non-updated model and the real or system under test.
///
/// Perform the checks post-execution - Throw in case of invalid state.
///
/// Update the model accordingly.
abstract run: m: 'Model * r: 'Real -> unit
/// Name of the command.
abstract toString: unit -> string
type IAsyncCommand<'Model,'Real> =
/// Check if the model is in the right state to apply the command.
abstract check: m: 'Model -> Async<bool>
/// Receive the non-updated model and the real or system under test.
///
/// Perform the checks post-execution - Throw in case of invalid state.
///
/// Update the model accordingly.
abstract run: m: 'Model * r: 'Real -> Async<unit>
/// Name of the command.
abstract toString: unit -> string
type IPromiseCommand<'Model,'Real> =
/// Check if the model is in the right state to apply the command.
abstract check: m: 'Model -> JS.Promise<bool>
/// Receive the non-updated model and the real or system under test.
///
/// Perform the checks post-execution - Throw in case of invalid state.
///
/// Update the model accordingly.
abstract run: m: 'Model * r: 'Real -> JS.Promise<unit>
/// Name of the command.
abstract toString: unit -> string
type ICommandWrapper<'Model,'Real> =
inherit ICommand<'Model,'Real>
/// The command to run.
abstract cmd: ICommand<'Model,'Real>
/// Indicates if the command has been executed.
abstract hasRan: bool with get, set
/// Create a clone of this command.
abstract clone: unit -> ICommandWrapper<'Model,'Real>
type IAsyncCommandWrapper<'Model,'Real> =
inherit IAsyncCommand<'Model,'Real>
/// The command to run.
abstract cmd: IAsyncCommand<'Model,'Real>
/// Indicates if the command has been executed.
abstract hasRan: bool with get, set
/// Create a clone of this command.
abstract clone: unit -> IAsyncCommandWrapper<'Model,'Real>
type IPromiseCommandWrapper<'Model,'Real> =
inherit IAsyncCommand<'Model,'Real>
/// The command to run.
abstract cmd: IAsyncCommand<'Model,'Real>
/// Indicates if the command has been executed.
abstract hasRan: bool with get, set
/// Create a clone of this command.
abstract clone: unit -> IPromiseCommandWrapper<'Model,'Real>
type ICommandSeq<'Model,'Real> =
inherit seq<ICommandWrapper<'Model,'Real>>
/// Collection of commandwrappers.
abstract commands: ResizeArray<ICommandWrapper<'Model,'Real>>
/// The meta-data given for a replay.
abstract metadataForReplay: (unit -> string)
/// Clone the command seq.
abstract clone: unit -> ICommandSeq<'Model,'Real>
/// The string representation of the command seq.
abstract toString: unit -> string
type IAsyncCommandSeq<'Model,'Real> =
inherit seq<IAsyncCommandWrapper<'Model,'Real>>
/// Collection of commandwrappers.
abstract commands: ResizeArray<IAsyncCommandWrapper<'Model,'Real>>
/// The meta-data given for a replay.
abstract metadataForReplay: (unit -> string)
/// Clone the command seq.
//[<Emit("[cloneMethod]()")>]
abstract clone: unit -> IAsyncCommandSeq<'Model,'Real>
/// The string representation of the command seq.
abstract toString: unit -> string
type IPromiseCommandSeq<'Model,'Real> =
inherit seq<IAsyncCommandWrapper<'Model,'Real>>
/// Collection of commandwrappers.
abstract commands: ResizeArray<IAsyncCommandWrapper<'Model,'Real>>
/// The meta-data given for a replay.
abstract metadataForReplay: (unit -> string)
/// Clone the command seq.
abstract clone: unit -> IPromiseCommandSeq<'Model,'Real>
/// The string representation of the command seq.
abstract toString: unit -> string
```
To do this in fast-check you must first either
already have or define a model to test:
```fsharp
type Model () =
let mutable count = 0
member _.Count =
let count = count
count
member _.Decrement () = count <- count - 1
member _.Increment () = count <- count + 1
```
The commands for this test will be an Arbitrary
that executes `Decrement()` and `Increment()` and
then validating the resulting state of the model
with what the expected state (these are your tests).
To make things easier you can add these:
```fsharp
type Msg =
| Decrement
| Increment
let update msg (model: Model) =
match msg with
| Decrement -> model.Decrement()
| Increment -> model.Increment()
```
Once we define a Msg to represent our commands and
update function to create a mapping of Msg to
the method we want to apply it's time to create the
commands to be transformed into an Arbitrary:
```fsharp
type DecrementCommand () =
interface ICommand<Model, Model> with
member _.check (m: Model) = true
member _.run (m: Model, r: Model) =
update Decrement r
Jest.expect(r.Count).toBeLessThanOrEqual(m.Count)
m.Decrement()
member _.toString () = "Decrement"
type IncrementCommand () =
interface ICommand<Model, Model> with
member _.check (m: Model) = true
member _.run (m: Model, r: Model) =
update Increment r
Jest.expect(r.Count).toBeGreaterThanOrEqual(m.Count)
m.Increment()
member _.toString () = "Increment"
```
All commands must interface either `ICommand<'Model,'Real>`
or `IAsyncCommand<'Model,'Real>`. Once we have these we can
now create the actual Arbitrary:
```fsharp
let commandArb = Arbitrary.commands [
Arbitrary.constant (DecrementCommand() :> ICommand<Model,Model>)
Arbitrary.constant (IncrementCommand() :> ICommand<Model,Model>)
]
```
Doing this all using async would look like this:
```fsharp
type AsyncDecrementCommand () =
interface IAsyncCommand<Model, Model> with
member _.check (m: Model) = async { return true }
member _.run (m: Model, r: Model) =
async {
update Decrement r
Jest.expect(r.Count).toBeLessThanOrEqual(m.Count)
m.Decrement()
}
member _.toString () = "Decrement"
type AsyncIncrementCommand () =
interface IAsyncCommand<Model, Model> with
member _.check (m: Model) = async { return true }
member _.run (m: Model, r: Model) =
async {
update Increment r
Jest.expect(r.Count).toBeGreaterThanOrEqual(m.Count)
m.Increment()
}
member _.toString () = "Increment"
let asyncCommandArb = Arbitrary.asyncCommands [
Arbitrary.constant (AsyncDecrementCommand() :> IAsyncCommand<Model,Model>)
Arbitrary.constant (AsyncIncrementCommand() :> IAsyncCommand<Model,Model>)
]
```
Doing this all using promises would look like this:
```fsharp
type PromiseDecrementCommand () =
interface IPromiseCommand<Model, Model> with
member _.check (m: Model) = promise { return true }
member _.run (m: Model, r: Model) =
promise {
update Decrement r
Jest.expect(r.Count).toBeLessThanOrEqual(m.Count)
m.Decrement()
}
member _.toString () = "Decrement"
type PromiseIncrementCommand () =
interface IPromiseCommand<Model, Model> with
member _.check (m: Model) = promise { return true }
member _.run (m: Model, r: Model) =
promise {
update Increment r
Jest.expect(r.Count).toBeGreaterThanOrEqual(m.Count)
m.Increment()
}
member _.toString () = "Increment"
let promiseCommandArb = Arbitrary.promiseCommands [
Arbitrary.constant (PromiseDecrementCommand() :> IPromiseCommand<Model,Model>)
Arbitrary.constant (PromiseIncrementCommand() :> IPromiseCommand<Model,Model>)
]
```
<Note>`Jest.expect` is not required here, the function just
needs to throw in the event of a (test) failure.</Note>
Finally once we have all of these pieces put together it's
time to run our tests. Depending on if you're running
`ICommand` or `IAsyncCommand` you will use [asyncModelRun](/fast-check#asyncmodelrun)
or [modelRun](/fast-check#modelrun).
```fsharp
// If you're using Fable.FastCheck.Jest
Jest.test.prop("Running some commands", commandArb, fun cmds ->
FastCheck.modelRun(Model(), Model(), cmds)
)
Jest.test("Running some commands", fun () ->
FastCheck.assert'(FastCheck.property(commandArb, fun cmds ->
FastCheck.modelRun(Model(), Model(), cmds)
))
)
```
For our async-based commands:
```fsharp
// If you're using Fable.FastCheck.Jest
Jest.test.prop("Running some commands", asyncCommandArb, fun cmds ->
FastCheck.asyncModelRun(Model(), Model(), cmds)
)
Jest.test("Running some commands", fun () ->
FastCheck.assert'(FastCheck.asyncProperty(asyncCommandArb, fun cmds ->
FastCheck.asyncModelRun(Model(), Model(), cmds)
))
)
```
For our promise-based commands:
```fsharp
// If you're using Fable.FastCheck.Jest
Jest.test.prop("Running some commands", promiseCommandArb, fun cmds ->
FastCheck.promiseModelRun(Model(), Model(), cmds)
)
Jest.test("Running some commands", fun () ->
FastCheck.assert'(FastCheck.promiseProperty(promiseCommandArb, fun cmds ->
FastCheck.promiseModelRun(Model(), Model(), cmds)
))
)
```

View File

@@ -0,0 +1,180 @@
# Scheduler
The scheduler arbitrary is a tool to test for the
presence of race-conditions in your code.
This feature is likely to be largely unused, as
these types of issues are hard to create in the
wonderful world of functional-programming.
The way this works is that the scheduler allows
you to wrap all async/promises used in your code which
then will be resolved in an arbitrary order. For
example, if you have two async/promises which one should
always resolve first (not by definition, but by
nature: like one sleeping for 1 second and the other
for 20 seconds) the scheduler may decide to
resolve the longer promise first.
There are two types of schedulers:
Shared between them:
```fsharp
type SchedulerReturnTask =
isDone: bool
isFaulty: bool
```
## AsyncScheduler
```fsharp
type AsyncSchedulerReturn =
isDone: bool
isFaulty: bool
task: Async<SchedulerReturnTask>
type AsyncScheduler =
/// Adds an async to the scheduler, returns the same
/// async that now runs in the context of the scheduler.
schedule (a: Async<'T>) -> Async<'T>
/// Adds a functions that generates asyncs to the scheduler, returns the same
/// async that now runs in the context of the scheduler.
scheduleFunction (f: 'Args -> Async<'T>) -> ('Args -> Async<'T>)
/// Adds a sequence of asyncs to the scheduler, returns the same
/// asyncs that now runs in the context of the scheduler.
scheduleSequence (funcs: seq<unit -> Async<obj>>) -> AsyncSchedulerReturn
scheduleSequence (funcs: seq<(unit -> Async<obj>) * string>) -> AsyncSchedulerReturn
/// Number of pending tasks waiting to be scheduled by the scheduler.
count: unit -> int
/// Wait for one promise to resolve in the scheduler.
///
/// Throws if there is no more pending tasks.
waitOne: unit -> Async<unit>
/// Tries to wait for one promise to resolve in the scheduler.
///
/// Returns None if there is no more pending tasks.
tryWaitOne: unit -> Async<unit> option
/// Wait all scheduled tasks, including the ones that might be created by one of
/// the resolved task.
///
/// Do not use if waitAll call has to be wrapped into an helper function such as act that can
/// relaunch new tasks afterwards.
waitAll: unit -> Async<unit>
```
Usage would look like this:
```fsharp
// If you're using Fable.FastCheck.Jest
Jest.test.prop("Scheduler runs async", Arbitrary.Defaults.asyncScheduler, fun s ->
async {
let one = s.schedule(async { return 1 })
let two = s.schedule(async { return 2 })
do! s.waitAll()
do! Jest.expect(one).toBe(1)
do! Jest.expect(two).toBe(2)
}
)
Jest.test("Scheduler runs async", fun () ->
FastCheck.assert'(FastCheck.asyncProperty(Arbitrary.Defaults.asyncScheduler, fun s ->
async {
let one = s.schedule(async { return 1 })
let two = s.schedule(async { return 2 })
do! s.waitAll()
do! Jest.expect(one).toBe(1)
do! Jest.expect(two).toBe(2)
}
))
)
```
## PromiseScheduler
```fsharp
type PromiseSchedulerReturn =
isDone: bool
isFaulty: bool
task: JS.Promise<SchedulerReturnTask>
type PromiseScheduler =
/// Adds a promise to the scheduler, returns the same
/// promise that now runs in the context of the scheduler.
schedule: (prom: JS.Promise<'T>) -> JS.Promise<'T>
/// Adds a functions that generates promises to the scheduler, returns the same
/// promise that now runs in the context of the scheduler.
scheduleFunction: (f: 'Args -> JS.Promise<'T>) -> 'Args -> JS.Promise<'T>
/// Adds a sequence of promises to the scheduler, returns the same
/// promises that now runs in the context of the scheduler.
scheduleSequence: (funcs: seq<unit -> JS.Promise<obj>>) -> PromiseSchedulerReturn
scheduleSequence: (funcs: seq<(unit -> JS.Promise<obj>) * string>) -> PromiseSchedulerReturn
/// Number of pending tasks waiting to be scheduled by the scheduler.
count: unit -> int
/// Wait for one promise to resolve in the scheduler.
///
/// Throws if there is no more pending tasks.
waitOne: unit -> JS.Promise<unit>
/// Tries to wait for one promise to resolve in the scheduler.
///
/// Returns None if there is no more pending tasks.
tryWaitOne: unit -> JS.Promise<unit> option
/// Wait all scheduled tasks, including the ones that might be created by one of the
/// resolved task.
///
/// Do not use if waitAll call has to be wrapped into an helper function such as act
/// that can relaunch new tasks afterwards.
waitAll: unit -> JS.Promise<unit>
```
Usage would look like this:
```fsharp
// If you're using Fable.FastCheck.Jest
Jest.test.prop("Scheduler runs promises", Arbitrary.Defaults.promiseScheduler, fun s ->
promise {
let one = s.schedule(promise { return 1 })
let two = s.schedule(promise { return 2 })
do! s.waitAll()
do! Jest.expect(one).resolves.toBe(1)
do! Jest.expect(two).resolves.toBe(2)
}
)
Jest.test("Scheduler runs promises", fun () ->
FastCheck.assert'(FastCheck.promiseProperty(Arbitrary.Defaults.promiseScheduler, fun s ->
promise {
let one = s.schedule(promise { return 1 })
let two = s.schedule(promise { return 2 })
do! s.waitAll()
do! Jest.expect(one).resolves.toBe(1)
do! Jest.expect(two).resolves.toBe(2)
}
))
)
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
docs/images/test.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

124
docs/index.html Normal file
View File

@@ -0,0 +1,124 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Fable.Jester</title>
<link rel="stylesheet" href="https://unpkg.com/docute@4/dist/docute.css">
<link rel="stylesheet" type="text/css" href="styles/website.css">
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/docute@4/dist/docute.js"></script>
<script>
// make images work using absolute urls both in developement and when published to github
Vue.component("ResolvedImage", {
props: ["source"],
template: "<img :src='resolvedUrl' />",
computed: {
resolvedUrl: function () {
let path = window.location.pathname;
if (path.endsWith("/")) {
return path.substr(0, path.length - 1) + this.source;
} else {
return path + this.source;
}
}
}
});
var docs = new Docute({
detectSystemDarkTheme: true,
darkThemeToggler: true,
imageZoom: true,
layout: "wide",
target: "#root",
highlight: ["fsharp", "bash", "powershell", "json"],
cssVariables: {
accentColor: "hsl(171, 100%, 41%)",
sidebarWidth: "300px",
sidebarLinkActiveColor: "hsl(171, 100%, 41%)",
sidebarLinkArrowColor: "hsl(171, 100%, 41%)"
},
nav: [
{ title: "GitHub Repository", link: "https://github.com/Shmew/Fable.Jester" }
],
sidebar: [
{
title: "Introduction",
link: "/"
},
{
title: "Installation",
links: [
{ title: "Fable.Jester", link: "/installation/jest" },
{ title: "Fable.ReactTestingLibrary", link: "/installation/react-testing-library" },
{ title: "Fable.FastCheck", link: "/installation/fast-check" },
{ title: "Fable.FastCheck.Jest", link: "/installation/fast-check-jest" }
]
},
{
title: "Release Notes",
link: "/RELEASE_NOTES"
},
{
title: "Fable.Jester",
links: [
{ title: "Introduction", link: "/jest/" },
{ title: "Describe", link: "/jest/describe" },
{ title: "Test", link: "/jest/test" },
{ title: "Globals", link: "/jest/globals" },
{ title: "Expect", link: "/jest/expect" },
{ title: "Expect Helpers", link: "/jest/expect-helpers" }
]
},
{
title: "Fable.ReactTestingLibrary",
links: [
{ title: "Introduction", link: "/rtl/" },
{ title: "RTL", link: "/rtl/rtl" },
{ title: "Queries", link: "/rtl/queries-for-element" },
{ title: "Render", link: "/rtl/render"}
]
},
{
title: "Fable.FastCheck",
links: [
{ title: "Introduction", link: "/fast-check/" },
{ title: "FastCheck", link: "/fast-check/fast-check" },
{ title: "Arbitrary", link: "/fast-check/arbitrary/" },
{ title: "Arbitrary.Defaults", link: "/fast-check/arbitrary/defaults" },
{ title: "Arbitrary.ConstrainedDefaults", link: "/fast-check/arbitrary/constrained-defaults" },
{ title: "Arbitrary.Array", link: "/fast-check/arbitrary/array" },
{ title: "Arbitrary.List", link: "/fast-check/arbitrary/list" },
{ title: "Arbitrary.ResizeArray", link: "/fast-check/arbitrary/resizearray" },
{ title: "Arbitrary.Map", link: "/fast-check/arbitrary/map" },
{ title: "Arbitrary.Set", link: "/fast-check/arbitrary/set" },
{ title: "Computation Expression", link: "/fast-check/arbitrary/ce" },
{ title: "Jest Extension", link: "/fast-check/jest-extension" },
{ title: "Model Testing", link: "/fast-check/model-testing" },
{ title: "Elmish Model Testing", link: "/fast-check/elmish-model-testing" },
{ title: "Scheduler", link: "/fast-check/scheduler" }
]
},
{
title: "Creating Tests",
link: "/creating-tests"
},
{
title: "Running Tests",
link: "/running-tests"
},
{
title: "Acknowledgment",
link: "/acknowledgment"
},
{
title: "Contributing",
link: "/contributing"
}
]
});
</script>
</body>
</html>

View File

@@ -0,0 +1,14 @@
# Fable.FastCheck.Jest
To install `Fable.FastCheck.Jest` you need to
add the nuget package into your F# project:
```bash
# nuget
dotnet add package Fable.FastCheck.Jest
# paket
paket add Fable.FastCheck.Jest --project ./project/path
```
There are no NPM dependencies for this project, those
are covered by [Fable.Jest](/installation/jest) and [Fable.FastCheck](/installation/fast-check).

View File

@@ -0,0 +1,35 @@
# Fable.FastCheck
To install `Fable.FastCheck` you need to add the
nuget package into your F# project:
```bash
# nuget
dotnet add package Fable.FastCheck
# paket
paket add Fable.FastCheck --project ./project/path
```
Then you need to install the corresponding npm dependencies.
```bash
npm install fast-check --save-dev
___
yarn add fast-check --dev
```
### Use Femto
If you happen to use [Femto], then it can
install everything for you in one go:
```bash
cd ./project
femto install Fable.FastCheck
```
Here, the nuget package will be installed
using the package manager that the project
is using (detected by Femto) and then the
required npm packages will be resolved
[Femto]: https://github.com/Zaid-Ajaj/Femto

48
docs/installation/jest.md Normal file
View File

@@ -0,0 +1,48 @@
# Fable.Jester
To install `Fable.Jester` to add the nuget
package into your F# project:
```bash
# nuget
dotnet add package Fable.Jester
# paket
paket add Fable.Jester --project ./project/path
```
Then you need to install the corresponding npm dependencies.
```bash
npm install jest --save-dev
npm install @testing-library/jest-dom --save-dev
npm install @babel/plugin-transform-modules-commonjs --save-dev // Recommended, but not necessary
npm install @sinonjs/fake-timers --save-dev // If you plan to use timer mocks
npm install prettier --save-dev // If you plan to use snapshot testing
___
yarn add jest --dev
yarn add @testing-library/jest-dom --dev
yarn add @babel/plugin-transform-modules-commonjs --dev // Recommended, but not necessary
yarn add @sinonjs/fake-timers --dev // If you plan to use timer mocks
yarn add prettier --dev // If you plan to use snapshot testing
```
### Use Femto
If you happen to use [Femto], then it can
install everything for you in one go:
```bash
cd ./project
femto install Fable.Jester
```
Here, the nuget package will be installed
using the package manager that the project
is using (detected by Femto) and then the
required npm packages will be resolved
Do note that this will *not* install the
optional dependencies listed above (the
babel plugin and prettier).
[Femto]: https://github.com/Zaid-Ajaj/Femto

View File

@@ -0,0 +1,49 @@
# Fable.ReactTestingLibrary
To install `Fable.ReactTestingLibrary` you need
to add the nuget package into your F# project:
```bash
# nuget
dotnet add package Fable.ReactTestingLibrary
# paket
paket add Fable.ReactTestingLibrary --project ./project/path
```
Then you need to install the corresponding npm dependencies.
```bash
npm install @testing-library/react --save-dev
npm install @testing-library/user-event --save-dev
npm install @babel/plugin-transform-modules-commonjs --save-dev // Recommended, but not necessary
___
yarn add @testing-library/react --dev
yarn add @testing-library/user-event --dev
yarn add @babel/plugin-transform-modules-commonjs --dev // Recommended, but not necessary
```
This library does not need the main package
to function, so it is possible to use with
[Fable.Mocha].
Do note that using this library standalone
will mean you have no access to any `expect`
methods that are commonly used with it.
### Use Femto
If you happen to use [Femto], then it can
install everything for you in one go:
```bash
cd ./project
femto install Fable.ReactTestingLibrary
```
Here, the nuget package will be installed using the package manager that the project is using (detected by Femto) and then the required npm packages will be resolved
Do note that this will *not* install the optional dependencies listed above (the babel plugin).
[Fable.Mocha]: https://github.com/Zaid-Ajaj/Fable.Mocha
[Femto]: https://github.com/Zaid-Ajaj/Femto

35
docs/jest/README.md Normal file
View File

@@ -0,0 +1,35 @@
# Fable.Jester
Fable.Jester are bindings to use [jest] and [jest-dom] to
test Fable applications.
The library has been developed to follow the native API as
closely as possible, while making some changes to allow for
better discoverablity.
Fable.Jester as a whole is exposed as two main types: `Jest`
and `expect`.
As long as you know of those two items you can find anything
that is available in this library. See those linked sections
for more details.
## Jest
The `Jest` type exposes almost every piece of functionality
in the library:
* [Describe blocks](/jest/describe)
* [Test blocks](/jest/test)
* [Global functions](/jest/globals)
* [Expect](/jest/expect)
## Expect
The [expect](/jest/expectHelpers) type is not the actual
method of making assertions, which can be confusing. The
purpose of this type is a collection of helper methods to
aid you when *actually* making your assertions.
[jest]: https://www.npmjs.com/package/jest
[jest-dom]: https://www.npmjs.com/package/@testing-library/jest-dom

120
docs/jest/describe.md Normal file
View File

@@ -0,0 +1,120 @@
# Describe
Tests in Jest are defined by module level `describe` blocks.
All tests should be wrapped in these based on some commonality.
You can think of this as your `testList` if you're famlilar
with [Expecto] or [Fable.Mocha].
There are three describes available:
* [describe](#describe-2)
* [describe.only](#describeonly)
* [describe.skip](#describeskip)
## describe
Runs all tests within the describe block and groups them
together in the results.
Signature:
```fsharp
(name: string, fn: unit -> unit) -> unit
```
You can use this like so:
```fsharp
Jest.describe("my test suite", (fun () ->
// tests go here
))
```
## describe.only
Only runs all tests within the describe block and groups
them together in the results.
The difference between this and [describe](#describe-2) is
hat *only* this block will
run within a given file. __All other describe blocks will be
skipped!__
Signature:
```fsharp
(name: string, fn: unit -> unit) -> unit
```
You can use this like so:
```fsharp
Jest.describe.only("my test suite", (fun () ->
// tests go here
))
```
## describe.skip
Runs __no tests within the describe block__.
Signature:
```fsharp
(name: string, fn: unit -> unit) -> unit
```
You can use this like so:
```fsharp
Jest.describe.skip("my test suite", (fun () ->
// tests go here, but they won't be executed.
))
```
What is the point of this function?
Good question! It is mostly when you need to control which
tests are executed based on some external factor.
For example if you have tests that will always fail in a CI
environment, you could match on an environment variable and
skip some tests based on the value.
## Where are the other describes?
If you're familiar with Jest, you may have noticed that
the `describe.each` family of `describe` is missing.
The reason for this is that it is really not useful for us.
Let's look at how it is used in the [Jest docs]:
```js
describe.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('.add(%i, %i)', (a, b, expected) => {
test(`returns ${expected}`, () => {
expect(a + b).toBe(expected);
});
});
```
If you want this type of functionality, it can be implemented like this:
```fsharp
Jest.describe("how to run a describe like describe.each", (fun () ->
let myTestCases = [
(1, 1, 2)
(1, 2, 3)
(2, 1, 3)
]
for (a, b, expected) in myTestCases do
Jest.test(sprintf "%i + %i returns %i" a b expected, (fun () ->
Jest.expect(a + b).toBe(expected)
))
))
```
[Expecto]: https://github.com/haf/expecto
[Fable.Mocha]: https://github.com/Zaid-Ajaj/Fable.Mocha
[Jest docs]: https://jestjs.io/docs/en/api#describeeachtablename-fn-timeout

190
docs/jest/expect-helpers.md Normal file
View File

@@ -0,0 +1,190 @@
# Expect
Jest exposes an `expect` object that is used to make
creating assertions easier.
## addSnapshotSerializer
Add a module that formats application-specific data structures.
Signature:
```fsharp
(serializer: obj) -> unit
```
Usage:
```fsharp
expect.addSnapshotSerializer(import "serializer" "my-serializer-module")
```
## any
Matches anything that was created with the given constructor.
Signature:
```fsharp
(value: 'Constructor)
```
Usage:
```fsharp
Jest.expect(myConstructedObj).toBe(expect.any(myConstructor)
```
## anything
Matches anything but null or undefined.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(1).toBe(expect.anything())
```
## arrayContaining
Matches a received collection which contains all of the elements
in the expected array. That is, the expected collection is a
subset of the received collection. Therefore, it matches a
received collection which contains elements that are not in the
expected collection.
Signature:
```fsharp
(values: ResizeArray<'T>)
(values: 'T [])
(values: 'T list)
(values: 'T seq)
```
Usage:
```fsharp
let arraySample = [| 1;2;3;4;5;6;7 |]
Jest.expect(arraySample).toEqual(expect.arrayContaining([| 2;3;4 |]))
```
## assertions
Verifies that a certain number of assertions are called
during a test.
Signature:
```fsharp
(number: int) -> unit
```
Usage:
```fsharp
expect.assertions(2)
```
## extend
Adds custom matchers to Jest.
See the [jest documentation](https://jestjs.io/docs/en/expect) for list
of `this` properties and methods
Signature:
```fsharp
(matchers: unit -> MatcherResponse) -> unit
(matchers: 'a -> MatcherResponse) -> unit
(matchers: 'a -> 'b -> MatcherResponse) -> unit
...
/// The response structure of matcher extensions.
type MatcherResponse =
abstract pass: bool
abstract message: unit -> string
```
Usage:
```fsharp
expect.extend(myExtension)
```
## hasAssertions
Verifies that at least one assertion is called during a test.
Signature:
```fsharp
unit -> unit
```
Usage:
```fsharp
expect.hasAssertions()
```
## not
Inverts the pass/fail status of a matcher.
Usage:
```fsharp
Jest.expect("test").toEqual(expect.not.stringContaining("whoa"))
```
## objectContaining
Matches any received object that recursively matches the
expected properties. That is, the expected object is a
subset of the received object. Therefore, it matches a
received object which contains properties that are
present in the expected object.
Signature:
```fsharp
(value: obj)
```
Usage:
```fsharp
let actual =
{| someValue = "test"
someOtherValue = "testValue" |}
|> Fable.Core.JsInterop.toPlainJsObj
let expected =
{| someValue = "test" |}
|> Fable.Core.JsInterop.toPlainJsObj
Jest.expect(actual).toEqual(expect.objectContaining(expected))
```
## stringContaining
Matches the received value if it is a string that
contains the exact expected string.
Signature:
```fsharp
(value: string)
```
Usage:
```fsharp
Jest.expect("test").toEqual(expect.stringContaining("te"))
```
## stringMatching
Matches the received value if it is a string that matches
the expected string or regular expression.
Signature:
```fsharp
(value: string)
(value: Regex)
```
Usage:
```fsharp
Jest.expect("test").toEqual(expect.stringMatching(Regex("test")))
```

892
docs/jest/expect.md Normal file
View File

@@ -0,0 +1,892 @@
# Expect
Jest and its ecosystem has many matchers available
to make writing tests as easy as possible.
To get started you need to start your expect:
```fs
Jest.expect(someValue)
```
From this point you can "dot" into all the matchers
that are valid for the given input.
## not
Inverts the pass/fail status of a matcher.
Usage:
```fsharp
Jest.expect(1).not.toBe(2)
```
## rejects
<Note>This is only available when the assertion is a promise</Note>
Unwrap the reason of a rejected promise so any other
matcher can be chained. If the promise is fulfilled
the assertion fails.
Usage:
```fsharp
Jest.expect(myPromise).rejects.toThrow()
```
## resolves
<Note>This is only available when the assertion is a promise</Note>
Unwrap the value of a fulfilled promise so any other
matcher can be chained. If the promise is rejected
the assertion fails.
This is automatically applied for `Async<'T>` values.
Usage:
```fsharp
Jest.expect(myPromise).resolves.toBe(1)
```
## toBe
Compare primitive values or to check referential identity
of object instances. It calls Object.is to compare values,
which is even better for testing than === strict equality
operator.
Signature:
```fsharp
(value: 'T)
```
Usage:
```fsharp
Jest.expect(1).toBe(2)
```
## toBeCloseTo
<Note>This is only available when the assertion is an `int`, `float`, or `decimal`</Note>
Compare floats or decimals for approximate equality.
Signature:
```fsharp
(number: decimal, ?numDigits: int)
(number: float, ?numDigits: int)
```
Usage:
```fsharp
Jest.expect(System.Math.PI).toBeCloseTo(3.14, 2)
```
## toBeChecked
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether the given element is checked.
It accepts an input of type checkbox or radio and elements with
a role of checkbox, radio, or switch with a valid aria-checked
attribute of "true" or "false".
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(myElement).toBeChecked()
```
## toBeDefined
Check that a variable is not undefined.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect("hi").toBeDefined()
```
## toBeDisabled
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether an element is disabled from the
user's perspective.
It matches if the element is a form control and the disabled
attribute is specified on this element or the element is a
descendant of a form element with a disabled attribute.
According to the specification, the following elements can be
actually disabled: button, input, select, textarea, optgroup,
option, and fieldset.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(myElement).toBeDisabled()
```
## toBeEnabled
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether an element is not disabled from the user's perspective.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(myElement).toBeEnabled()
```
## toBeEmpty
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether an element has content or not.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(myElement).toBeEmpty()
```
## toBeFalsy
Matcher for when you don't care what a value is and you want to
ensure a value is false in a boolean context.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(()).toBeFalsy()
```
## toBeGreaterThan
<Note>This is only available when the assertion is a number primative such as `int` or `float`</Note>
To compare received > expected.
Signature:
```fsharp
(number: decimal)
(number: float)
(number: int)
(number: int64)
```
Usage:
```fsharp
Jest.expect(3).toBeGreaterThan(2)
```
## toBeGreaterThanOrEqual
<Note>This is only available when the assertion is a number primative such as `int` or `float`</Note>
To compare received >= expected.
Signature:
```fsharp
(number: decimal)
(number: float)
(number: int)
(number: int64)
```
Usage:
```fsharp
Jest.expect(3).toBeGreaterThanOrEqual(3)
```
## toBeInTheDocument
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether an element is present in the document or not.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(myElement).toBeInTheDocument()
```
## toBeInvalid
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check if a form element, or the entire form, is currently invalid.
An input, select, textarea, or form element is invalid if it has an
aria-invalid attribute with no value or a value of "true", or if the
result of checkValidity() is false.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(myElement).toBeInvalid()
```
## toBeLessThan
<Note>This is only available when the assertion is a number primative such as `int` or `float`</Note>
To compare received < expected.
Signature:
```fsharp
(number: decimal)
(number: float)
(number: int)
(number: int64)
```
Usage:
```fsharp
Jest.expect(2).toBeLessThan(3)
```
## toBeLessThanOrEqual
<Note>This is only available when the assertion is a number primative such as `int` or `float`</Note>
To compare received <= expected.
Signature:
```fsharp
(number: decimal)
(number: float)
(number: int)
(number: int64)
```
Usage:
```fsharp
Jest.expect(3).toBeLessThanOrEqual(3)
```
## toBeNaN
Check that a value is NaN.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(Fable.Core.JS.NaN).toBeNaN()
```
## toBeNull
Check that something is null.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(myString).toBeNull()
```
## toBeRequired
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check if a form element is currently required.
An element is required if it is having a required or
aria-required="true" attribute.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(myElement).toBeRequired()
```
## toBeTruthy
Matcher for when you don't care what a value is and you want to
ensure a value is true in a boolean context.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect([|1;2;3|]).toBeTruthy()
```
## toBeUndefined
Check that a variable is undefined.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(myObj).toBeUndefined()
```
## toBeValid
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check if the value of a form element, or the entire form, is currently valid.
An input, select, textarea, or form element is valid if it has no aria-invalid
attribute or an attribute value of "false". The result of checkValidity() must
also be true.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(myElement).toBeValid()
```
## toBeVisible
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
This allows you to check if an element is currently visible to the user.
An element is visible if all the following conditions are met:
* Does not have its css property display set to none.
* Does not have its css property visibility set to either hidden or collapse.
* Does not have its css property opacity set to 0.
* The parent element is also visible (and so on up to the top of the DOM tree).
* Does not have the hidden attribute.
* If `<details />` it has the open attribute.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(myElement).toBeVisible()
```
## toContain
Check that an item is in a collection.
Note that this matcher will check *both* the
index and values if given a int or float.
These will *both* pass:
```fsharp
expect([1;2;5]).toContain(5)
expect([1;2;5]).toContain(3)
```
If you do not want this, see [toContainEqual](#tocontainequal).
Signature:
```fsharp
(item: 'T)
```
Usage:
```fsharp
Jest.expect([1;2;3]).toContain(1)
Jest.expect([1;2;8]).toContain(8)
Jest.expect(["I";"Like";"Pie"]).toContain("Pie")
```
## toContainElement
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether an element contains another element as a descendant or not.
Signature:
```fsharp
(element: HTMLElement)
(element: Node)
```
Usage:
```fsharp
Jest.expect(myElement).toContainElement(myExpectedElement)
```
## toContainHTML
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether a string representing a HTML element is contained in another element
Signature:
```fsharp
(htmlText: string)
```
Usage:
```fsharp
Jest.expect(myElement).toContainHTML("<div></div>")
```
## toContainEqual
Check that an item with a specific structure and
values is contained in a collection.
Signature:
```fsharp
(item: 'T)
```
Usage:
```fsharp
let testList = [
{| One = 1
Two = 2
Three = 3 |}
{| One = 4
Two = 5
Three = 6 |}
]
Jest.expect(testList).toContainEqual({| One = 1; Two = 2; Three = 3 |})
```
## toEqual
Compare recursively all properties of object instances
(also known as "deep" equality). It calls Object.is to
compare primitive values, which is even better for
testing than === strict equality operator.
Signature:
```fsharp
(value: 'T)
```
Usage:
```fsharp
Jest.expect(true).toEqual(true)
```
## toHaveAttribute
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether the given element has an attribute or not.
You can also optionally check that the attribute has a specific expected value
or partial match using [expect.stringContaining] or [expect.stringMatching].
Signature:
```fsharp
(attr: string, ?value: obj)
```
Usage:
```fsharp
Jest.expect(myElement).toHaveAttribute("style")
```
## toHaveClass
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether the given element has certain classes within its class attribute.
You must provide at least one class, unless you are asserting that an element does
not have any classes.
Signature:
```fsharp
([<ParamArray>] classNames: string [])
```
Usage:
```fsharp
Jest.expect(myElement).toHaveClass("myDiv")
```
## toHaveDescription
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether the given element has a description or not.
An element gets its description via the aria-describedby attribute. Set this to
the id of one or more other elements. These elements may be nested inside, be
outside, or a sibling of the passed in element.
Whitespace is normalized. Using multiple ids will join the referenced elements
text content separated by a space.
Signature:
```fsharp
(value: Regex)
(value: string)
```
Usage:
```fsharp
Jest.expect(myElement).toHaveDescription("Hello!")
```
## toHaveDisplayValue
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether the given form element has the specified displayed value (the
one the end user will see).
It accepts input, select and textarea elements with the exception of
`<input type="checkbox">` and `<input type="radio">`, which can be meaningfully
matched only using [toBeChecked] or [toHaveFormValues].
Signature:
```fsharp
(value: Regex)
(value: string)
(values: ResizeArray<Regex>)
(values: ResizeArray<string>)
(values: Regex [])
(values: string [])
(values: Regex list)
(values: string list)
(values: Regex seq)
(values: string seq)
```
Usage:
```fsharp
Jest.expect(myElement).toHaveDisplayValue("Hello!")
```
## toHaveFocus
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether an element has focus or not.
Signature:
```fsharp
unit
```
Usage:
```fsharp
Jest.expect(myElement).toHaveClass()
```
## toHaveFormValues
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check if a form or fieldset contains form controls for each given name, and having the specified value.
Note that this matcher can *only* be invoked on a form or fieldset element.
Signature:
```fsharp
(expectedValues: obj)
(expectedValues: ResizeArray<string * obj>)
(expectedValues: (string * obj) [])
(expectedValues: (string * obj) list)
(expectedValues: (string * obj) seq)
```
Usage:
```fsharp
let formValues = [
"username", box "Shmew"
"remember me", box true
]
Jest.expect(myElement).toHaveFormValues(formValues)
```
## toHaveLength
Check that an object has a .length property and it is set
to a certain numeric value.
Signature:
```fsharp
(length: int)
```
Usage:
```fsharp
Jest.expect("hi").toHaveLength(2)
```
## toHaveProperty
You can provide an optional value argument to compare the received
property value (recursively for all properties of object instances,
also known as deep equality, like the toEqual matcher).
Signature:
```fsharp
(keyPath: string, ?value: 'T)
(keyPath: ResizeArray<string>)
(keyPath: ResizeArray<string>, value: 'T)
(keyPath: string [])
(keyPath: string [], value: 'T)
(keyPath: string list)
(keyPath: string list, value: 'T)
(keyPath: string seq)
(keyPath: string seq, value: 'T)
```
Usage:
```fsharp
Jest.expect({| test = "testValue" |} |> toPlainJsObj).toHaveProperty("test")
```
## toHaveStyle
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check if a certain element has some specific css properties with specific values applied.
It matches only if the element has all the expected properties applied, not just some of them.
Signature:
```fsharp
(css: obj)
(css: string)
(css: IStyleAttribute)
(css: IStyleAttribute list)
```
Usage:
```fsharp
let divStyle = style.backgroundColor (color.red)
Jest.expect(myElement).toHaveStyle(divStyle)
```
## toHaveTextContent
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether the given element has a text content or not.
When a string argument is passed through, it will perform a partial case-sensitive match to
the element content.
To perform a case-insensitive match, you can use a RegExp with the /i modifier.
If you want to match the whole content, you can use a RegExp to do it.
Signature:
```fsharp
(text: string)
(text: string, normalizeWhitespace: bool)
(text: Regex)
(text: Regex, normalizeWhitespace: bool)
```
Usage:
```fsharp
Jest.expect(myElement).toHaveTextContent(Regex("\\w+?"))
```
## toHaveValue
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Check whether the given form element has the specified value.
It accepts input, select and textarea elements with the exception of of `<input type="checkbox">`
and `<input type="radio">`, which can be meaningfully matched only using [toBeChecked] or [toHaveFormValues].
Signature:
```fsharp
(value: bool)
(value: float)
(value: System.Guid)
(value: int)
(value: string)
(value: ResizeArray<string>)
(value: string [])
(value: string list)
(value: string seq)
```
Usage:
```fsharp
Jest.expect(myElement).toHaveValue("hunter2")
```
## toMatch
<Note>This is only available when the assertion is a `string`</Note>
Check that a string matches a string or regular expression.
When using a string it is the same as doing "mystring".Contains(value)
Signature:
```fsharp
(value: string)
(value: Regex)
```
Usage:
```fsharp
Jest.expect("test").toMatch("test")
```
## toMatchObject
Check that a JavaScript object matches a subset of the properties of an object.
Signature:
```fsharp
(object: 'T)
```
Usage:
```fsharp
let actual = {| test = "hi" |}
let expected = {| test = "hi" |}
Jest.expect(actual).toMatchObject(expected)
```
## toMatchSnapshot
<Note>This is only available when the assertion is an `HTMLElement` or `Node`</Note>
Ensures that a value matches the most recent snapshot.
Signature:
```fsharp
(?propertyMatchers, ?hint)
```
Usage:
```fsharp
Jest.expect(myElement).toMatchSnapshot()
```
## toStrictEqual
Check that an object has the same types as well as structure.
Differences from .toEqual:
Keys with undefined properties are checked. e.g. {a: undefined, b: 2} does
not match {b: 2} when using .toStrictEqual.
Array sparseness is checked. e.g. [, 1] does not match [undefined, 1] when
using .toStrictEqual.
Object types are checked to be equal. e.g. A class instance with fields a
and b will not equal a literal object with fields a and b.
Signature:
```fsharp
(value: 'T)
```
Usage:
```fsharp
let actual = {| test = "hi" |}
let expected = {| test = "hiya" |}
Jest.expect(actual).not.toStrictEqual(expected)
```
## toThrow
Check that a function throws when called.
Signature:
```fsharp
unit
(err: exn)
(err: Regex)
(err: string)
```
Usage:
```fsharp
Jest.expect(myFailingFunction).toThrow(System.Exception("uh oh!"))
```
## Where are the other expect matchers?
Some matchers were chosen not to be included in this
library.
The reason for this being three-fold:
* The missing matchers are things like testing that a mocked call
was called N number of times. Well desgined F# code *shouldn't need
mocks*. It's my opinion that if you need a mock for a test, your
test is *already failing*. If you find that you *really* need this
functionality, create an [issue] and we can review/discuss it.
* The F# language invalidates the need for the assertion.
* It's additional work to maintain. ;)
[issue]: https://github.com/Shmew/Fable.Jester/issues/new/choose
[expect.stringContaining]: /jest/expect-helpers#stringcontaining
[expect.stringMatching]: /jest/expect-helpers#stringmatching

352
docs/jest/globals.md Normal file
View File

@@ -0,0 +1,352 @@
# Globals in Jest
Jest exposes global functions to use when writing your
tests. Instead of needing to keeps the docs open, all
of the functions are exposed from the `Jest` type.
Jest also has a `jest` object with additional helpers,
this has been merged into `Jest` so everything is readily
accessible.
## advanceTimersToNextTimer
Advances all timers by the needed milliseconds so that only
the next timeouts/intervals will run.
Optionally, you can provide steps, so it will run steps
amount of next timeouts/intervals.
<Note type="warning">Requires [fake-timers].</Note>
Signature:
```fsharp
(?steps: int) -> unit
```
Usage:
```fsharp
Jest.advanceTimersToNextTimer()
Jest.advanceTimersToNextTimer(2)
```
## afterAll
Runs a function after all the tests in this file have completed.
If the function returns a promise or is a generator, Jest waits
for that promise to resolve before continuing.
Optionally, you can provide a timeout (in milliseconds) for
specifying how long to wait before aborting.
The default timeout is 5 seconds.
Signature:
```fsharp
(fn: unit -> unit, ?timeout: int) -> unit
```
Usage:
```fsharp
Jest.afterAll((fun () -> printfn "Hello world!"))
```
## afterEach
Runs a function after each one of the tests in this file completes.
If the function returns a promise or is a generator, Jest waits
for that promise to resolve before continuing.
Optionally, you can provide a timeout (in milliseconds) for
specifying how long to wait before aborting.
The default timeout is 5 seconds.
Signature:
```fsharp
(fn: unit -> unit, ?timeout: int) -> unit
```
Usage:
```fsharp
Jest.afterEach((fun () -> printfn "Hello world!"))
```
## beforeAll
Runs a function before any of the tests in this file run.
If the function returns a promise or is a generator,
Jest waits for that promise to resolve before running tests.
Optionally, you can provide a timeout (in milliseconds) for
specifying how long to wait before aborting.
The default timeout is 5 seconds.
Signature:
```fsharp
(fn: unit -> unit, ?timeout: int) -> unit
```
Usage:
```fsharp
Jest.beforeAll((fun () -> printfn "Hello world!"))
```
## beforeEach
Runs a function before each of the tests in this file runs.
If the function returns a promise or is a generator,
Jest waits for that promise to resolve before running the test.
Optionally, you can provide a timeout (in milliseconds) for
specifying how long to wait before aborting.
The default timeout is 5 seconds.
Signature:
```fsharp
(fn: unit -> unit, ?timeout: int) -> unit
```
Usage:
```fsharp
Jest.beforeEach((fun () -> printfn "Hello world!"))
```
## clearAllTimers
Removes any pending timers from the timer system.
<Note type="warning">Requires [fake-timers].</Note>
Signature:
```fsharp
unit -> unit
```
Usage:
```fsharp
Jest.clearAllTimers()
```
## getRealSystemTime
When mocking time, `Date.now()` will also be mocked. If
you for some reason need access to the real current time,
you can invoke this function.
<Note type="warning">Requires [fake-timers].</Note>
Signature:
```fsharp
unit -> int64
```
Usage:
```fsharp
Jest.getRealSystemTime()
```
## getTimerCount
Returns the number of fake timers still left to run.
<Note type="warning">Requires [fake-timers].</Note>
Signature:
```fsharp
unit -> int
```
Usage:
```fsharp
Jest.getTimerCount()
```
## retryTimes
Runs failed tests n-times until they pass or until the max number
of retries is exhausted.
<Note type="warning">Requires [jest-circus].</Note>
Signature:
```fsharp
int -> unit
```
Usage:
```fsharp
Jest.retryTimes(10)
```
## runAllImmediates
Exhausts all tasks queued by setImmediate().
<Note type="warning">Requires [fake-timers].</Note>
Signature:
```fsharp
unit -> unit
```
Usage:
```fsharp
Jest.runAllImmediates()
```
## runAllTicks
Exhausts the micro-task queue (usually interfaced in node
via process.nextTick).
<Note type="warning">Requires [fake-timers].</Note>
Signature:
```fsharp
unit -> unit
```
Usage:
```fsharp
Jest.runAllTicks()
```
## runAllTimers
Exhausts both the macro-task queue (i.e., all tasks queued by
setTimeout(), setInterval(), and setImmediate()) and the
micro-task queue (usually interfaced in node via process.nextTick).
<Note type="warning">Requires [fake-timers].</Note>
Signature:
```fsharp
unit -> unit
```
Usage:
```fsharp
Jest.runAllTimers()
```
## runOnlyPendingTimers
Executes only the macro-tasks that are currently pending (i.e.,
only the tasks that have been queued by setTimeout() or
setInterval() up to this point).
If any of the currently pending macro-tasks schedule new
macro-tasks, those new tasks will not be executed by this call.
<Note type="warning">Requires [fake-timers].</Note>
Signature:
```fsharp
unit -> unit
```
Usage:
```fsharp
Jest.runOnlyPendingTimers()
```
## runTimersToTime
Executes only the macro task queue (i.e. all tasks queued by
setTimeout() or setInterval() and setImmediate()).
<Note type="warning">Requires [fake-timers].</Note>
Signature:
```fsharp
int -> unit
```
Usage:
```fsharp
Jest.runTimersToTime(10)
```
## setSystemTime
Set the current system time used by fake timers. Simulates a user
changing the system clock while your program is running.
It affects the current time but it does not in itself cause e.g.
timers to fire; they will fire exactly as they would have done
without the call to `setSystemTime`.
<Note type="warning">Requires [fake-timers].</Note>
Signature:
```fsharp
// Defaults to 0
unit -> unit
(ticks: int) -> unit
(ticks: int64) -> unit
```
Usage:
```fsharp
Jest.setSystemTime()
```
## setTimeout
Set the default timeout interval for tests and before/after
hooks in milliseconds.
The default timeout interval is 5 seconds if this method is not called.
If you want to set the timeout for all test files, a good place to
do this is in setupFilesAfterEnv.
<Note type="warning">Requires [fake-timers].</Note>
Signature:
```fsharp
(msToRun:int) -> unit
```
Usage:
```fsharp
Jest.setTimeout(10)
```
## useFakeTimers
Instructs Jest to use fake versions of the standard timer
functions (setTimeout, setInterval, clearTimeout,
clearInterval, nextTick, setImmediate and clearImmediate).
<Note type="warning">Requires [fake-timers].</Note>
Signature:
```fsharp
unit -> unit
```
Usage:
```fsharp
Jest.useFakeTimers()
```
## Where are the other globals?
Some functions were chosen not to be included in this
library.
The reason for this being two-fold:
* The missing functions are things like module and function mocking.
Well desgined F# code *shouldn't need mocks*. It's my opinion
that if you need a mock for a test, your test is *already failing*.
If you find that you *really* need this functionality, create an [issue]
and we can review/discuss it.
* It's additional work to maintain. ;)
[fake-timers]: https://github.com/sinonjs/fake-timers
[jest-circus]: https://www.npmjs.com/package/jest-circus
[issue]: https://github.com/Shmew/Fable.Jester/issues/new/choose

191
docs/jest/test.md Normal file
View File

@@ -0,0 +1,191 @@
# Test
The `test` is the core functionality of Jest, and where you
make all of your assertions.
There are four tests available:
* [test](#test-2)
* [test.only](#testonly)
* [test.skip](#testskip)
* [test.todo](#testtodo)
## test
Runs all tests within the test block and groups them together
in the results.
Signatures:
```fsharp
(name: string, fn: unit -> unit) -> unit
(name: string, fn: unit -> JS.Promise<unit>) -> unit
(name: string, fn: unit -> Async<unit>) -> unit
(name: string, fn: JS.Promise<unit>) -> unit
(name: string, fn: Async<unit>) -> unit
```
You can use this like so:
```fsharp
Jest.test("my test", (fun () ->
// assertions go here
))
Jest.test("my test", promise {
do! Jest.expect(myPromise).resolves.toBe(1)
})
Jest.test("my test", async {
do! Jest.expect(myAsync).toBe(1)
})
```
## test.only
The difference between this and [test](#test-2) is that *only*
this test will execute within a given describe block. __All
other test blocks will be skipped!__
Signatures:
```fsharp
(name: string, fn: unit -> unit) -> unit
(name: string, fn: unit -> JS.Promise<unit>) -> unit
(name: string, fn: unit -> Async<unit>) -> unit
(name: string, fn: JS.Promise<unit>) -> unit
(name: string, fn: Async<unit>) -> unit
```
You can use this like so:
```fsharp
Jest.test.only("my test", (fun () ->
// assertions go here
))
Jest.test.only("my test", promise {
do! Jest.expect(myPromise).resolves.toBe(1)
})
Jest.test.only("my test", async {
do! Jest.expect(myAsync).toBe(1)
})
```
## test.skip
__Runs no assertions within the test block__.
Signatures:
```fsharp
(name: string, fn: unit -> unit) -> unit
(name: string, fn: unit -> JS.Promise<unit>) -> unit
(name: string, fn: unit -> Async<unit>) -> unit
(name: string, fn: JS.Promise<unit>) -> unit
(name: string, fn: Async<unit>) -> unit
```
You can use this like so:
```fsharp
Jest.test.skip("my test", (fun () ->
// assertions go here, but won't be executed
))
Jest.test.skip("my test", promise {
// will not assert
do! Jest.expect(myPromise).resolves.toBe(1)
})
Jest.test.skip("my test", async {
// will not assert
do! Jest.expect(myAsync).toBe(1)
})
```
What is the point of this function?
Good question! It is mostly when you need to control which
tests are executed based on some external factor.
For example if you have tests that will always fail in a CI
environment, you could match on an environment variable and
skip some tests based on the value.
## test.todo
This is a placeholder so you can remember to implement a test
at a later point in time.
Signature:
```fsharp
(name: string) -> unit
```
You can use this like so:
```fsharp
Jest.test.todo "Do this!"
```
When you run your tests the summary will include a count of
tests that still need to be done.
<resolved-image source='/images/jest/test-summary.png' />
## Multiple assertions
You can make multiple assertions for a test by simply running
them in your test block.
One thing to note is that if your test is making multiple
assertions that include __promises__ or __async__ you __must
use the promise or async computation expressions!__ If you
do not do this the assertions can either not get run, or cause
your entire test suite to have wildly different results each
run.
The `Async` computation expression is overloaded to resolve
expected promises.
```fsharp
Jest.test("my test", promise {
do! Jest.expect(myPromise).resolves.not.toBe(2)
do! Jest.expect(myPromise).resolves.toBe(1)
})
Jest.test("my test", async {
do! Jest.expect(myPromise).resolves.toBe(1)
do! Jest.expect(myAsync).toBe(1)
})
```
## Where are the other tests?
This is the same logic as the
[above section on describes](#where-are-the-other-describes)
The reason for this is that it is really not useful for us.
Let's look at how it is used in the [Jest docs]:
```js
test.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('.add(%i, %i)', (a, b, expected) => {
expect(a + b).toBe(expected);
});
```
If you want this type of functionality, it can be implemented
like this:
```fsharp
Jest.test("same functionality as test.each", (fun () ->
for (input, output) in [|(1, 2);(2, 3);(3, 4)|] do
Jest.expect(input + 1).toEqual(output)
))
```
[Jest docs]: https://jestjs.io/docs/en/api#testeachtablename-fn-timeout

17
docs/rtl/README.md Normal file
View File

@@ -0,0 +1,17 @@
# Fable.ReactTestingLibrary
Fable.ReactTestingLibrary are bindings to use [react-testing-library]
and [user-event] to test Fable applications.
The library has been developed to follow the native API as
closely as possible, while making some changes to allow for
better discoverablity.
Fable.ReactTestingLibrary for the most part is exposed via `RTL`.
There are some additional types exposed in the main namespace.
However, these are all options used to provide input to `RTL`
functions, and will be noted where applicable.
[react-testing-library]: https://www.npmjs.com/package/@testing-library/react
[user-event]: https://www.npmjs.com/package/@testing-library/user-event

View File

@@ -0,0 +1,188 @@
# queriesForElement
The `queriesForElement` type exposes many methods of querying
the DOM for elements to test/compare against.
There are six different categories of queries:
* [getBy](#getby)
* [getAll](#getall)
* [queryBy](#queryby)
* [queryAll](#queryall)
* [findBy](#findby)
* [findAll](#findall)
Each of the query types have different targets for the attribute
you want to query for:
* [AltText](#alttext)
* [DisplayValue](#displayvalue)
* [LabelText](#labeltext)
* [PlaceholderText](#placeholdertext)
* [Role](#role)
* [Text](#text)
* [TestId](#testid)
* [Title](#title)
## Query Types
### getBy
The getBy query type returns the first matching node for a
query.
This will throw an error if no elements match, or
if more than one match is found.
### getAll
The getAll query type returns a list of all matching nodes
of the query.
This will throw an error if no elements match.
### queryBy
The queryBy query type returns the first matching node for a
query.
This will return an `Option` for the element.
### queryAll
The queryAll query type returns a list of all matching nodes
of the query.
This will return an empty list if there are no matches.
### findBy
The findBy query type returns the first matching node for a
query.
This returns a `JS.Promise` for the element which will reject
if no matches are found after the default timeout of 4500ms.
### findAll
The findAll query type returns a list of all matching nodes
of the query.
This returns a `JS.Promise` for the matches which will reject
if no matches are found after the default timeout of 4500ms.
## Query Targets
### AltText
Signature:
```fsharp
(matcher: string, ?selector : string, ?ignore: string, ?exact: bool, ?normalizer: string -> string)
(matcher: Regex, ?selector : string, ?ignore: string, ?exact: bool, ?normalizer: string -> string)
(matcher: string * HTMLElement -> bool, ?selector : string, ?ignore: string, ?exact: bool,
?normalizer: string -> string)
```
Usage:
```fsharp
RTL.screen.getByAltText("howdy!")
```
### DisplayValue
Signature:
```fsharp
(matcher: string, ?exact: bool, ?normalizer: string -> string)
(matcher: Regex, ?exact: bool, ?normalizer: string -> string)
(matcher: string * HTMLElement -> bool, ?exact: bool, ?normalizer: string -> string)
```
Usage:
```fsharp
RTL.screen.getAllByDisplayValue("howdy!")
```
### LabelText
Signature:
```fsharp
(matcher: string, ?selector : string, ?exact: bool, ?normalizer: string -> string)
(matcher: Regex, ?selector : string, ?exact: bool, ?normalizer: string -> string)
(matcher: string * HTMLElement -> bool, ?selector : string, ?exact: bool,
?normalizer: string -> string)
```
Usage:
```fsharp
RTL.screen.queryByLabelText("howdy!")
```
### PlaceholderText
Signature:
```fsharp
(matcher: string, ?exact: bool, ?normalizer: string -> string)
(matcher: Regex, ?exact: bool, ?normalizer: string -> string)
(matcher: string * HTMLElement -> bool, ?exact: bool, ?normalizer: string -> string)
```
Usage:
```fsharp
RTL.screen.queryAllByPlaceholderText("howdy!")
```
### Role
Signature:
```fsharp
(matcher: string, ?exact: bool, ?normalizer: string -> string)
(matcher: Regex, ?exact: bool, ?normalizer: string -> string)
(matcher: string * HTMLElement -> bool, ?exact: bool, ?normalizer: string -> string)
```
Usage:
```fsharp
RTL.screen.findByRole("howdy!")
```
### Text
Signature:
```fsharp
(matcher: string, ?selector : string, ?ignore: string, ?exact: bool, ?normalizer: string -> string)
(matcher: Regex, ?selector : string, ?ignore: string, ?exact: bool, ?normalizer: string -> string)
(matcher: string * HTMLElement -> bool, ?selector : string, ?ignore: string, ?exact: bool,
?normalizer: string -> string)
```
Usage:
```fsharp
RTL.screen.findAllByText("howdy!")
```
### TestId
Signature:
```fsharp
(matcher: string, ?exact: bool, ?normalizer: string -> string)
(matcher: Regex, ?exact: bool, ?normalizer: string -> string)
(matcher: string * HTMLElement -> bool, ?exact: bool, ?normalizer: string -> string)
```
Usage:
```fsharp
RTL.screen.getByTestId("howdy!")
```
### Title
Signature:
```fsharp
(matcher: string, ?exact: bool, ?normalizer: string -> string)
(matcher: Regex, ?exact: bool, ?normalizer: string -> string)
(matcher: string * HTMLElement -> bool, ?exact: bool, ?normalizer: string -> string)
```
Usage:
```fsharp
RTL.screen.getAllByTitle("howdy!")
```

169
docs/rtl/render.md Normal file
View File

@@ -0,0 +1,169 @@
# render
The `render` type returned after calling `RTL.render` is one of
the most used aspects of react-testing-library. The main reason
for this being that the `ReactElement` has now been rendered, and
what you have now is a type that inherits [queriesForElement] as
well as exposes a few additional functions and properties that
will make testing your React application easier.
## Render Options
When creating your render object you can pass in options to
modify the behavior:
```fsharp
RTL.render(myReactElement, [
renderOption.hydrate true
])
```
### container
By default, React Testing Library will create a div and append that
div to the document.body and this is where your React component will
be rendered. If you provide your own HTMLElement container via this
option, it will not be appended to the document.body automatically.
Signature:
```fsharp
(value: HTMLElement)
```
### baseElement
By default, React Testing Library will create a div and append that
div to the document.body and this is where your React component will
be rendered. If you provide your own HTMLElement container via this
option, it will not be appended to the document.body automatically.
Signature:
```fsharp
(value: HTMLElement)
```
### hydrate
By default, React Testing Library will create a div and append that
div to the document.body and this is where your React component will
be rendered. If you provide your own HTMLElement container via this
option, it will not be appended to the document.body automatically.
Signature:
```fsharp
(value: bool)
```
### wrapper
By default, React Testing Library will create a div and append that
div to the document.body and this is where your React component will
be rendered. If you provide your own HTMLElement container via this
option, it will not be appended to the document.body automatically.
Signature:
```fsharp
(value: ReactElement)
```
## baseElement
The containing DOM node where your React Element is rendered in
the container. If you don't specify the baseElement in the options
of render, it will default to document.body.
This is useful when the component you want to test renders something
outside the container div, e.g. when you want to snapshot test your
portal component which renders its HTML directly in the body.
Signature:
```fsharp
property: HTMLElement
```
Usage:
```fsharp
renderer.baseElement
```
## container
The containing DOM node of your rendered React Element (rendered
using ReactDOM.render). It's a div. This is a regular DOM node,
so you can call container.querySelector etc. to inspect the children.
Signature:
```fsharp
property: HTMLElement
```
Usage:
```fsharp
renderer.container
```
## asFragment
Returns a DocumentFragment of your rendered component. This can be
useful if you need to avoid live bindings and see how your component
reacts to events.
Signature:
```fsharp
unit -> DocumentFragment
```
Usage:
```fsharp
renderer.asFragment()
```
## debug
This method is a shortcut for console.log(prettyDOM(baseElement))
Signature:
```fsharp
unit -> unit
```
Usage:
```fsharp
renderer.debug()
```
## rerender
It'd probably be better if you test the component that's doing the
prop updating to ensure that the props are being updated correctly
(see the Guiding Principles section). That said, if you'd prefer to
update the props of a rendered component in your test, this function
can be used to update props of the rendered component.
Signature:
```fsharp
(reactElement: ReactElement) -> unit
```
Usage:
```fsharp
renderer.rerender(myReactElement)
```
## unmount
This will cause the rendered component to be unmounted. This is useful
for testing what happens when your component is removed from the page
(like testing that you don't leave event handlers hanging around
causing memory leaks).
Signature:
```fsharp
unit -> unit
```
Usage:
```fsharp
renderer.unmount()
```
[queriesForElement]: /rtl/queries-for-element

680
docs/rtl/rtl.md Normal file
View File

@@ -0,0 +1,680 @@
# RTL
RTL is the entry point to using all functionality of
Fable.ReactTestingLibrary.
## act
This is a light wrapper around the react-dom/test-utils act function.
All it does is forward all arguments to the act function if your
version of react supports act.
Signature:
```fsharp
(callback: unit -> unit) -> unit
```
You can use this like so:
```fsharp
RTL.act(fun () ->
ReactDOM.render(myReactElement, myContainer)
)
```
## cleanup
Unmounts React trees that were mounted with render.
<Note>This is done automatically in Jest after each test.</Note>
Signature:
```fsharp
unit -> unit
```
You can use this like so:
```fsharp
RTL.cleanup()
```
## configure
Set the configuration options.
Signature:
```fsharp
(options: IConfigureOption list) -> unit
// ConfigureOptions
type configureOption:
/// The default value for the hidden option used by getByRole.
///
/// Defaults to false.
defaultHidden: (value: bool)
/// A function that returns the error used when getBy* or getAllBy* fail.
/// Takes the error message and container object as arguments.
getElementError (handler: string * HTMLElement -> exn)
/// The attribute used by getByTestId and related queries.
///
/// Defaults to data-testid.
testIdAttribute (value: string)
```
You can use this like so:
```fsharp
RTL.configure [
configureOption.defaultHidden true
configureOption.testIdAttribute "myAttribute"
]
```
## createEvent.`event`
Convenience methods for creating DOM events that can then be fired by
fireEvent, allowing you to have a reference to the event created: this
might be useful if you need to access event properties that cannot be
initiated programmatically (such as timeStamp).
`HTMLElement` is extended to also support these methods.
Signature:
```fsharp
/// The event property type is based on the type of event being created.
(element: HTMLElement, ?eventProperties: <EventProperty> list) -> Event
```
You can use this like so:
```fsharp
RTL.createEvent.change(elem, ([
event.target [
prop.value newTime
]
]))
elem.createEvent.change([
event.target [
prop.value newTime
]
])
```
## fireEvent
Fires a DOM event.
Signature:
```fsharp
(element: HTMLElement, event: #Browser.Types.Event) -> unit
```
You can use this like so:
```fsharp
RTL.fireEvent(elem, myEvent)
```
## fireEvent.`event`
Convenience methods for firing DOM events.
`HTMLElement` is extended to also support these methods.
Signature:
```fsharp
/// The event property type is based on the type of event being created.
(element: HTMLElement, ?eventProperties: <EventProperty> list) -> Event
```
You can use this like so:
```fsharp
RTL.fireEvent.change(elem, [
event.target [
prop.value "Hello world"
]
])
elem.fireEvent.change([
event.target [
prop.value "Hello world"
]
])
```
## getNodeText
Gets the text of the element.
Signature:
```fsharp
(element: HTMLElement) -> string
```
You can use this like so:
```fsharp
RTL.getNodeText(myElem)
```
## getRoles
Allows iteration over the implicit ARIA roles represented
in a given tree of DOM nodes.
It returns an object, indexed by role name, with each value being an
array of elements which have that implicit ARIA role.
Signature:
```fsharp
(element: HTMLElement) -> obj
```
You can use this like so:
```fsharp
RTL.getRoles(myElem)
```
## isInaccessible
Compute if the given element should be excluded from the accessibility
API by the browser.
It implements every MUST criteria from the Excluding Elements from the
Accessibility Tree section in WAI-ARIA 1.2 with the exception of
checking the role attribute.
Signature:
```fsharp
(element: HTMLElement) -> bool
```
You can use this like so:
```fsharp
RTL.isInaccessible(myElem)
```
## logRoles
Print out a list of all the implicit ARIA roles within a tree of DOM
nodes, each role containing a list of all of the nodes which match
that role.
Signature:
```fsharp
(element: HTMLElement) -> unit
```
You can use this like so:
```fsharp
RTL.logRoles(myElem)
```
## prettyDOM
Returns a readable representation of the DOM tree of a node.
Signature:
```fsharp
(element: HTMLElement) -> string
(node: Node) -> string
(element: HTMLElement, maxLength: int) -> string
(element: HTMLElement, options: IPrettyDOMOption list) -> string
(element: HTMLElement, maxLength: int, options: IPrettyDOMOption list) -> string
// prettyDOM options
type prettyDOMOption:
/// Call toJSON method (if it exists) on objects.
callToJSON (value: bool)
/// Escape special characters in regular expressions.
escapeRegex (value: bool)
/// Escape special characters in strings.
escapeString (value: bool)
/// Highlight syntax with colors in terminal (some plugins).
highlight (value: bool)
/// Spaces in each level of indentation.
indent (value: int)
/// Levels to print in arrays, objects, elements, .. etc.
maxDepth (value: int)
/// Minimize added space: no indentation nor line breaks.
min (value: bool)
/// Plugins to serialize application-specific data types.
plugins (value: seq<string>)
/// Include or omit the name of a function.
printFunctionName (value: bool)
/// Colors to highlight syntax in terminal.
theme (properties: IPrettyDOMThemeOption list)
// prettyDOM theme options
// Only accessible from prettyDOMOption module
type theme:
/// Default: "gray"
comment (value: string)
/// Default: "reset"
content (value: string)
/// Default: "yellow"
prop (value: string)
/// Default: "cyan"
tag (value: string)
/// Default: "green"
value (value: string)
```
You can use this like so:
```fsharp
RTL.prettyDOM(myElem, [
prettyDOMOption.callToJSON true
prettyDOMOption.highlight true
prettyDOMOption.theme [
prettyDOMOption.theme.comment (color.red)
]
])
```
## render
Render into a container which is appended to document.body.
See [render](/rtl/render) for more details.
Signature:
```fsharp
(reactElement: ReactElement) -> render
(reactElement: ReactElement, options: IRenderOption list) -> render
// Render options
type renderOption:
/// By default, React Testing Library will create a div and
/// append that div to the document.body and this is where
/// your React component will be rendered. If you provide
/// your own HTMLElement container via this option, it will
/// not be appended to the document.body automatically.
container (value: HTMLElement)
/// If the container is specified, then this defaults to that,
/// otherwise this defaults to document.documentElement. This
/// is used as the base element for the queries as well as what
/// is printed when you use debug().
baseElement (value: HTMLElement)
/// If hydrate is set to true, then it will render with
/// ReactDOM.hydrate. This may be useful if you are using
/// server-side rendering and use ReactDOM.hydrate to mount
/// your components.
hydrate (value: bool)
/// Pass a React Component as the wrapper option to have it
/// rendered around the inner element. This is most useful for
/// creating reusable custom render functions for common data
/// providers.
wrapper (value: ReactElement)
```
You can use this like so:
```fsharp
RTL.render(myReactElement)
RTL.render(myReactElement, [
renderOption.hydrate true
])
```
## screen
Queries bound to the document.body
See [queries](/rtl/queries-for-element) for more details.
You can use this like so:
```fsharp
RTL.screen.getByTestId("my-test-id")
```
## userEvent.clear
Convenience method for using [fireEvent](#fireevent).
Selects the text inside an input or textarea and deletes it.
`HTMLElement` is extended to also support these methods.
Signature:
```fsharp
(element: HTMLElement) -> unit
```
You can use this like so:
```fsharp
RTL.userEvent.clear(myElement)
myElement.userEvent.clear()
```
## userEvent.click
Convenience method for using [fireEvent](#fireevent).
Clicks element, depending on what element is it can have different side effects.
`HTMLElement` is extended to also support these methods.
Signature:
```fsharp
(element: HTMLElement) -> unit
```
You can use this like so:
```fsharp
RTL.userEvent.click(myElement)
myElement.userEvent.click()
```
## userEvent.ctrlClick
Convenience method for using [fireEvent](#fireevent).
Clicks element while holding the control key, depending on what
element is it can have different side effects.
`HTMLElement` is extended to also support these methods.
Signature:
```fsharp
(element: HTMLElement) -> unit
```
You can use this like so:
```fsharp
RTL.userEvent.ctrlClick(myElement)
myElement.userEvent.ctrlClick()
```
## userEvent.dblClick
Convenience method for using [fireEvent](#fireevent).
Clicks element twice, depending on what element is it can have different side effects.
`HTMLElement` is extended to also support these methods.
Signature:
```fsharp
(element: HTMLElement) -> unit
```
You can use this like so:
```fsharp
RTL.userEvent.dblClick(myElement)
myElement.userEvent.dblClick()
```
## userEvent.selectOptions
Convenience method for using [fireEvent](#fireevent).
Selects the specified option(s) of a `<select>` or a `<select multiple>` element.
`HTMLElement` is extended to also support these methods.
Signature:
```fsharp
(element: HTMLElement, values: 'T []) -> unit
(element: HTMLElement, values: 'T list) -> unit
(element: HTMLElement, values: ResizeArray<'T>) -> unit
```
You can use this like so:
```fsharp
RTL.userEvent.selectOptions(myElement, ["Peach"])
myElement.userEvent.selectOptions(["Peach"])
```
## userEvent.shiftClick
Convenience method for using [fireEvent](#fireevent).
Clicks element while holding the shift key, depending on what
element is it can have different side effects.
`HTMLElement` is extended to also support these methods.
Signature:
```fsharp
(element: HTMLElement) -> unit
```
You can use this like so:
```fsharp
RTL.userEvent.shiftClick(myElement)
myElement.userEvent.shiftClick()
```
## userEvent.shiftCtrlClick
Convenience method for using [fireEvent](#fireevent).
Clicks element while holding the shift and control keys, depending on what
element is it can have different side effects.
`HTMLElement` is extended to also support these methods.
Signature:
```fsharp
(element: HTMLElement) -> unit
```
You can use this like so:
```fsharp
RTL.userEvent.shiftCtrlClick(myElement)
myElement.userEvent.shiftCtrlClick()
```
## userEvent.tab
Convenience method for using [fireEvent](#fireevent).
Fires a tab event changing the document.activeElement in the same way the browser does.
`HTMLElement` is extended to also support these methods.
Signature:
```fsharp
(shift: bool, focusTrap: HTMLElement) -> unit
```
You can use this like so:
```fsharp
RTL.userEvent.tab(false, myElement)
myElement.userEvent.tab(false)
```
## userEvent.type'
Convenience method for using [fireEvent](#fireevent).
Writes text inside an `<input>` or a `<textarea>`.
`HTMLElement` is extended to also support these methods.
Signature:
```fsharp
(element: HTMLElement, text: string) -> JS.Promise<unit>
(element: HTMLElement, text: string, allAtOnce: bool) -> JS.Promise<unit>
(element: HTMLElement, text: string, delay: int) -> JS.Promise<unit>
(element: HTMLElement, text: string, allAtOnce: bool, delay: int) -> JS.Promise<unit>
```
You can use this like so:
```fsharp
RTL.userEvent.type'(myElement, "oh, hello there!")
myElement.userEvent.type'("oh, hello there!")
```
## waitFor
When in need to wait for any period of time you can use waitFor, to wait for your expectations to pass.
Signature:
```fsharp
(callback: unit -> unit) -> JS.Promise<unit>
(callback: unit -> unit, waitForOptions: IWaitOption list) -> JS.Promise<unit>
// waitFor options
type waitForOption:
/// The default container is the global document.
///
/// Make sure the elements you wait for are descendants of container.
container (element: HTMLElement)
/// The default interval is 50ms.
///
/// However it will run your callback immediately before starting the intervals.
interval (value: int)
/// Sets the configuration of the mutation observer.
mutationObserver (options: IMutationObserverOption list)
/// The default timeout is 1000ms.
timeout (value: int)
// waitFor mutationObserver Options
// Only accessible from waitForOption module
type mutationObserver
/// An array of specific attribute names to be monitored.
///
/// If this property isn't included, changes to all attributes cause mutation
/// notifications.
attributeFiler (filter: string)
attributeFiler (filters: string list)
/// An array of specific attribute names to be monitored.
///
/// If this property isn't included, changes to all attributes cause mutation
/// notifications.
attributeOldValue (value: bool)
/// Set to true to watch for changes to the value of attributes on the node
/// or nodes being monitored.
///
/// The default value is false.
attributes (value: bool)
/// Set to true to monitor the specified target node or subtree for changes
/// to the character data contained within the node or nodes.
///
/// The default value is `false` via omission.
characterData (value: bool)
/// Set to true to record the previous value of a node's text whenever the
/// text changes on nodes being monitored.
///
/// The default value is `false` via omission.
characterDataOldValue (value: bool)
/// Set to true to monitor the target node (and, if subtree is true, its
/// descendants) for the addition of
/// new child nodes or removal of existing child nodes.
///
/// The default is false.
childList (value: bool)
/// Set to true to extend monitoring to the entire subtree of nodes rooted
/// at target. All of the other MutationObserverInit properties are then
/// extended to all of the nodes in the subtree instead of applying
/// solely to the target node.
///
/// The default value is false.
subtree (value: bool)
```
You can use this like so:
```fsharp
promise {
...
return! RTL.waitFor(fun () -> Jest.expect(myHtmlElement).toHaveFocus())
}
```
## waitForElementToBeRemoved
Wait for the removal of element(s) from the DOM.
See [waitFor](#waitfor) for details on options.
Signature:
```fsharp
(callback: unit -> #HTMLElement option) -> JS.Promise<unit>
(callback: unit -> #HTMLElement list) -> JS.Promise<unit>
(callback: unit -> #HTMLElement option, waitForOptions: IWaitOption list) -> JS.Promise<unit>
(callback: unit -> #HTMLElement list, waitForOptions: IWaitOption list) -> JS.Promise<unit>
```
You can use this like so:
```fsharp
promise {
...
return! RTL.waitForElementToBeRemoved(fun () -> RTL.screen.queryByTestId("myElementTestId"))
}
```
## within
Takes a DOM element and binds it to the raw query functions,
allowing them to be used without specifying a container.
See [queries](/rtl/queries-for-element) for more details.
Signature:
```fsharp
(element: HTMLElement) -> queriesForElement
```
You can use this like so:
```fsharp
RTL.within(myElement)
```

59
docs/running-tests.md Normal file
View File

@@ -0,0 +1,59 @@
# Running Tests
Once you've created your project and done the necessary
configuration, all that's left is to run it!
The steps are relatively simple:
## package.json
In your `package.json` you will want to create some script
tasks to compile your tests. Then you will want a task to
run your scripts.
Here is an example:
```json
...
"scripts": {
"pretestJest": "fable-splitter -c tests/Fable.Jester.Tests/splitter.config.js",
"pretestRTL": "fable-splitter -c tests/Fable.ReactTestingLibrary.Tests/splitter.config.js",
"test": "yarn pretestJest && yarn pretestRTL && yarn jest",
"testJest": "yarn pretestJest && yarn jest",
"testRTL": "yarn pretestRTL && yarn jest"
},
...
```
When you want to run your tests you can simply do `yarn test`.
<Note>I do recommend you use a [FAKE] script to clean your tests directory</Note>
[FAKE]: https://github.com/fsharp/FAKE
### Snapshot Testing
If you are doing snapshot testing you will want to add
a jest configuration to point to your test directory.
The reason for this is that when doing your snapshot
testing, jest will see the `.js.snap` file in your project
directory, and warn you about having an obsolete snapshot.
Here is an example:
```json
...
"jest": {
"roots": [
"./dist/tests"
]
}
}
```
## Test Output
When it's time to finally run your tests you should see output like this:
<resolved-image source='/images/test.gif' />

3
docs/styles/website.css Normal file
View File

@@ -0,0 +1,3 @@
.Content {
max-width: 1000px !important;
}

70
package.json Normal file
View File

@@ -0,0 +1,70 @@
{
"name": "fable.-signal-r",
"version": "0.0.1",
"description": "Fable and server bindings for SignalR.",
"homepage": "https://github.com/Shmew/Fable.SignalR",
"bugs": {
"url": "https://github.com/Shmew/Feliz.UseBridge/issues/new/choose"
},
"license": "MIT",
"author": "Cody Johnson",
"repository": {
"type": "git",
"url": "https://github.com/Shmew/Fable.SignalR"
},
"scripts": {
"build": "webpack -p",
"dev": "webpack-dev-server",
"pretest": "fable-splitter -c tests/Fable.SignalR.Tests/splitter.config.js",
"publish-docs": "node publish.js",
"start": "live-server --port=8080 docs/",
"test": "yarn pretest && yarn jest"
},
"dependencies": {
"@microsoft/signalr": "^3.1.4",
"react": "^16",
"react-dom": "^16"
},
"devDependencies": {
"@babel/core": "^7",
"@babel/plugin-transform-modules-commonjs": "^7",
"@babel/plugin-transform-regenerator": "^7",
"@babel/preset-env": "^7",
"@sinonjs/fake-timers": "^6",
"@testing-library/jest-dom": "^5",
"@testing-library/react": "^10",
"@testing-library/user-event": "^10",
"babel-loader": "^8",
"clean-webpack-plugin": "^3",
"copy-webpack-plugin": "^5",
"core-js": "^3",
"css-loader": "^3",
"fable-compiler": "^2",
"fable-loader": "^2",
"fable-splitter": "^2",
"fast-check": "^1",
"file-loader": "^4",
"gh-pages": "^2",
"html-webpack-plugin": "^3",
"jest": "^26",
"live-server": "^1",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4",
"prettier": "^2",
"remotedev": "^0.2.9",
"resolve-url-loader": "^3",
"sass": "^1",
"sass-loader": "^7",
"save": "^2",
"style-loader": "^1",
"webpack": "^4",
"webpack-cli": "^3",
"webpack-dev-server": "^3"
},
"private": true,
"jest": {
"roots": [
"./dist/tests"
]
}
}

136
paket.dependencies Normal file
View File

@@ -0,0 +1,136 @@
version 5.241.2
framework: auto-detect
group Fable.SignalR
source https://nuget.org/api/v2
source https://api.nuget.org/v3/index.json
nuget Fable.Core ~> 3
nuget Fable.Promise ~> 2
nuget Fable.SimpleJson ~> 3
nuget FSharp.Core ~> 4.7
group Fable.SignalR.AspNetCore
source https://nuget.org/api/v2
source https://api.nuget.org/v3/index.json
nuget Fable.Remoting.Json ~> 2
nuget FSharp.Core ~> 4.7
nuget Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson ~> 3
group Fable.SignalR.Elmish
source https://nuget.org/api/v2
source https://api.nuget.org/v3/index.json
nuget Fable.Core ~> 3
nuget Fable.Elmish ~> 3
nuget Fable.Promise ~> 2
nuget FSharp.Core ~> 4.7
group Fable.SignalR.Feliz
source https://nuget.org/api/v2
source https://api.nuget.org/v3/index.json
nuget Fable.Core ~> 3
nuget Fable.Promise ~> 2
nuget Feliz ~> 1
nuget FSharp.Core ~> 4.7
group Fable.SignalR.Saturn
source https://nuget.org/api/v2
source https://api.nuget.org/v3/index.json
nuget Fable.Remoting.Json ~> 2
nuget FSharp.Core ~> 4.7
nuget Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson ~> 3
nuget Saturn ~> 0
group Fable.SignalR.Server
source https://nuget.org/api/v2
source https://api.nuget.org/v3/index.json
nuget Fable.Remoting.Json ~> 2
nuget FSharp.Core ~> 4.7
nuget Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson ~> 3
group Fable.SignalR.Tests
source https://nuget.org/api/v2
source https://api.nuget.org/v3/index.json
nuget Fable.Browser.Dom ~> 1
nuget Fable.Browser.Url ~> 1
nuget Fable.Browser.WebSocket ~> 1
nuget Fable.Core ~> 3
nuget Fable.FastCheck ~> 0
nuget Fable.FastCheck.Jest ~> 0
nuget Fable.Jester ~> 0
nuget Fable.ReactTestingLibrary ~> 0
nuget Fable.Promise ~> 2
nuget Feliz ~> 0
nuget FSharp.Core ~> 4.7
group Client
source https://nuget.org/api/v2
source https://api.nuget.org/v3/index.json
nuget Fable.Browser.Dom ~> 1
nuget Fable.Browser.Url ~> 1
nuget Fable.Browser.WebSocket ~> 1
nuget Elmish.Bridge.Client ~> 3
nuget Fable.Core ~> 3
nuget Fable.Elmish ~> 3
nuget Fable.Elmish.Debugger ~> 3
nuget Fable.Elmish.HMR ~> 4
nuget Fable.Elmish.React ~> 3
nuget Fable.Promise ~> 2
nuget Fable.React ~> 6
nuget Fable.SimpleJson ~> 3
nuget Feliz ~> 1
nuget Feliz.Recoil ~> 0
nuget Feliz.UseElmish ~> 1
nuget FSharp.Core ~> 4.7
group Server
source https://nuget.org/api/v2
source https://api.nuget.org/v3/index.json
nuget FSharp.Control.AsyncSeq ~> 2
nuget FSharp.Core ~> 4.7
nuget Saturn ~> 0
nuget TaskBuilder.fs 2.2.0-alpha
group Shared
source https://nuget.org/api/v2
source https://api.nuget.org/v3/index.json
nuget FSharp.Core ~> 4.7.1
group Tooling
source https://nuget.org/api/v2
source https://api.nuget.org/v3/index.json
nuget Fake.IO.FileSystem ~> 5
nuget FSharp.Core ~> 4.7
nuget FSharp.Json ~> 0
nuget FSharpLint.Core ~> 0
nuget Yarnpkg.Yarn ~> 1
clitool dotnet-fable ~> 2
clitool dotnet-fake ~> 5
group FakeBuild
source https://api.nuget.org/v3/index.json
nuget Fake.Core.Target ~> 5
nuget Fake.Core.ReleaseNotes ~> 5
nuget Fake.DotNet.AssemblyInfoFile ~> 5
nuget Fake.DotNet.Cli ~> 5
nuget Fake.DotNet.MSBuild ~> 5
nuget Fake.DotNet.Paket ~> 5
nuget Fake.IO.FileSystem ~> 5
nuget Fake.JavaScript.Yarn ~> 5
nuget Fake.JavaScript.Npm ~> 5
nuget Fake.Tools.Git ~> 5
nuget FSharp.Json ~> 0
nuget FSharpLint.Core ~> 0

2049
paket.lock Normal file

File diff suppressed because one or more lines are too long

3
paket.references Normal file
View File

@@ -0,0 +1,3 @@
group Tooling
dotnet-fake
dotnet-fable

15
publish.js Normal file
View File

@@ -0,0 +1,15 @@
var ghPages = require("gh-pages");
var packageUrl = "https://github.com/Shmew/Fable.SignalR.git";
console.log("Publishing to ", packageUrl);
ghPages.publish("docs", {
repo: packageUrl
}, function (e) {
if (e === undefined) {
console.log("Finished publishing succesfully");
} else {
console.log("Error occured while publishing :(", e);
}
});

View File

@@ -0,0 +1,167 @@
namespace Fable.SignalR
[<AutoOpen>]
module SignalRExtension =
open Fable.Remoting.Json
open Fable.SignalR
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.SignalR
open Microsoft.AspNetCore.Routing
open Microsoft.Extensions.DependencyInjection
open System.Collections.Generic
open System.Threading.Tasks
type IServiceCollection with
member this.AddSignalR(settings: SignalR.Settings<'ClientApi,'ServerApi>) =
let config =
let hubOptions = settings.Config |> Option.bind (fun s -> s.HubOptions)
match settings.Config with
| Some { OnConnected = Some onConnect; OnDisconnected = None } ->
{| Transient = FableHub.OnConnected.addTransient onConnect settings.Update
HubOptions = hubOptions |}
| Some { OnConnected = None; OnDisconnected = Some onDisconnect } ->
{| Transient = FableHub.OnDisconnected.addTransient onDisconnect settings.Update
HubOptions = hubOptions |}
| Some { OnConnected = Some onConnect; OnDisconnected = Some onDisconnect } ->
{| Transient = FableHub.Both.addTransient onConnect onDisconnect settings.Update
HubOptions = hubOptions |}
| _ ->
{| Transient = FableHub.addTransient settings.Update
HubOptions = hubOptions |}
match config.HubOptions with
| Some hubOptions ->
this
.AddSignalR()
.AddNewtonsoftJsonProtocol(fun o -> o.PayloadSerializerSettings.Converters.Add(FableJsonConverter()))
.AddHubOptions<FableHub<'ClientApi,'ServerApi>>(System.Action<HubOptions<FableHub<'ClientApi,'ServerApi>>>(hubOptions))
.Services |> config.Transient
| None ->
this
.AddSignalR()
.AddNewtonsoftJsonProtocol(fun o -> o.PayloadSerializerSettings.Converters.Add(FableJsonConverter()))
.Services |> config.Transient
member this.AddSignalR(settings: SignalR.Stream.Settings<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi>) =
let config =
let hubOptions = settings.Config |> Option.bind (fun s -> s.HubOptions)
match settings.Config with
| Some { OnConnected = Some onConnect; OnDisconnected = None } ->
{| Transient = FableHub.Stream.OnConnected.addTransient onConnect settings.Update settings.Stream
HubOptions = hubOptions |}
| Some { OnConnected = None; OnDisconnected = Some onDisconnect } ->
{| Transient = FableHub.Stream.OnDisconnected.addTransient onDisconnect settings.Update settings.Stream
HubOptions = hubOptions |}
| Some { OnConnected = Some onConnect; OnDisconnected = Some onDisconnect } ->
{| Transient = FableHub.Stream.Both.addTransient onConnect onDisconnect settings.Update settings.Stream
HubOptions = hubOptions |}
| _ ->
{| Transient = FableHub.Stream.addTransient settings.Update settings.Stream
HubOptions = hubOptions |}
match config.HubOptions with
| Some hubOptions ->
this
.AddSignalR()
.AddNewtonsoftJsonProtocol(fun o -> o.PayloadSerializerSettings.Converters.Add(FableJsonConverter()))
.AddHubOptions<StreamingFableHub<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi>>(
System.Action<HubOptions<StreamingFableHub<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi>>>(hubOptions))
.Services |> config.Transient
| None ->
this
.AddSignalR()
.AddNewtonsoftJsonProtocol(fun o -> o.PayloadSerializerSettings.Converters.Add(FableJsonConverter()))
.Services |> config.Transient
member this.AddSignalR(endpoint: string, update: 'ClientApi -> FableHub<'ClientApi,'ServerApi> -> Task) =
SignalR.ConfigBuilder(endpoint, update).Build()
|> this.AddSignalR
member this.AddSignalR
(endpoint: string,
update: 'ClientApi -> StreamingFableHub<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi> -> Task,
stream: 'ClientStreamApi -> StreamingFableHub<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi> ->
IAsyncEnumerable<'StreamServerApi>) =
SignalR.Stream.ConfigBuilder(endpoint, update, stream).Build()
|> this.AddSignalR
member this.AddSignalR
(endpoint: string,
update: 'ClientApi -> FableHub<'ClientApi,'ServerApi> -> Task,
config: SignalR.ConfigBuilder<'ClientApi,'ServerApi> -> SignalR.ConfigBuilder<'ClientApi,'ServerApi>) =
SignalR.ConfigBuilder(endpoint, update)
|> config
|> fun res -> res.Build()
|> this.AddSignalR
member this.AddSignalR
(endpoint: string,
update: 'ClientApi -> StreamingFableHub<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi> -> Task,
stream: 'ClientStreamApi -> StreamingFableHub<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi> ->
IAsyncEnumerable<'StreamServerApi>,
config: SignalR.Stream.ConfigBuilder<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi> ->
SignalR.Stream.ConfigBuilder<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi>) =
SignalR.Stream.ConfigBuilder(endpoint, update, stream)
|> config
|> fun res -> res.Build()
|> this.AddSignalR
type IApplicationBuilder with
member this.UseSignalR(settings: SignalR.Settings<'ClientApi,'ServerApi>) =
let config =
match settings.Config with
| Some { OnConnected = Some _; OnDisconnected = None } ->
fun (endpoints: IEndpointRouteBuilder) ->
endpoints.MapHub<FableHub.OnConnected<'ClientApi,'ServerApi>>(settings.EndpointPattern)
|> SignalR.Config.bindEnpointConfig settings.Config
| Some { OnConnected = None; OnDisconnected = Some _ } ->
fun (endpoints: IEndpointRouteBuilder) ->
endpoints.MapHub<FableHub.OnDisconnected<'ClientApi,'ServerApi>>(settings.EndpointPattern)
|> SignalR.Config.bindEnpointConfig settings.Config
| Some { OnConnected = Some _; OnDisconnected = Some _ } ->
fun (endpoints: IEndpointRouteBuilder) ->
endpoints.MapHub<FableHub.Both<'ClientApi,'ServerApi>>(settings.EndpointPattern)
|> SignalR.Config.bindEnpointConfig settings.Config
| _ ->
fun (endpoints: IEndpointRouteBuilder) ->
endpoints.MapHub<FableHub<'ClientApi,'ServerApi>>(settings.EndpointPattern)
|> SignalR.Config.bindEnpointConfig settings.Config
this
.UseRouting()
// fsharplint:disable-next-line
.UseEndpoints(fun endpoints -> endpoints |> config |> ignore)
member this.UseSignalR(settings: SignalR.Stream.Settings<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi>) =
let config =
match settings.Config with
| Some { OnConnected = Some _; OnDisconnected = None } ->
fun (endpoints: IEndpointRouteBuilder) ->
endpoints.MapHub<FableHub.Stream.OnConnected<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi>>(settings.EndpointPattern)
|> SignalR.Stream.Config.bindEnpointConfig settings.Config
| Some { OnConnected = None; OnDisconnected = Some _ } ->
fun (endpoints: IEndpointRouteBuilder) ->
endpoints.MapHub<FableHub.Stream.OnDisconnected<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi>>(settings.EndpointPattern)
|> SignalR.Stream.Config.bindEnpointConfig settings.Config
| Some { OnConnected = Some _; OnDisconnected = Some _ } ->
fun (endpoints: IEndpointRouteBuilder) ->
endpoints.MapHub<FableHub.Stream.Both<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi>>(settings.EndpointPattern)
|> SignalR.Stream.Config.bindEnpointConfig settings.Config
| _ ->
fun (endpoints: IEndpointRouteBuilder) ->
endpoints.MapHub<StreamingFableHub<'ClientApi,'ClientStreamApi,'ServerApi,'StreamServerApi>>(settings.EndpointPattern)
|> SignalR.Stream.Config.bindEnpointConfig settings.Config
this
.UseRouting()
// fsharplint:disable-next-line
.UseEndpoints(fun endpoints -> endpoints |> config |> ignore)

View File

@@ -0,0 +1,22 @@
// Auto-Generated by FAKE; do not edit
namespace System
open System.Reflection
open System.Runtime.CompilerServices
[<assembly: AssemblyTitleAttribute("Fable.SignalR.AspNetCore")>]
[<assembly: AssemblyProductAttribute("Fable.SignalR")>]
[<assembly: AssemblyDescriptionAttribute("Fable and server bindings for SignalR.")>]
[<assembly: AssemblyVersionAttribute("0.0.1")>]
[<assembly: AssemblyFileVersionAttribute("0.0.1")>]
[<assembly: AssemblyConfigurationAttribute("Release")>]
[<assembly: InternalsVisibleToAttribute("Fable.SignalR.AspNetCore.Tests")>]
do ()
module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "Fable.SignalR.AspNetCore"
let [<Literal>] AssemblyProduct = "Fable.SignalR"
let [<Literal>] AssemblyDescription = "Fable and server bindings for SignalR."
let [<Literal>] AssemblyVersion = "0.0.1"
let [<Literal>] AssemblyFileVersion = "0.0.1"
let [<Literal>] AssemblyConfiguration = "Release"
let [<Literal>] InternalsVisibleTo = "Fable.SignalR.AspNetCore.Tests"

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="AspNetCore.fs" />
<None Include="paket.references" />
<None Include="paket.template" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Fable.SignalR.Server\Fable.SignalR.Server.fsproj" />
</ItemGroup>
<Import Project="..\..\.paket\Paket.Restore.targets" />
</Project>

View File

@@ -0,0 +1,4 @@
group Fable.SignalR.AspNetCore
Fable.Remoting.Json
FSharp.Core
Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson

View File

@@ -0,0 +1,12 @@
type project
licenseExpression https://github.com/Shmew/Feliz.UseBridge/blob/master/LICENSE
authors Cody Johnson
description
A Feliz friendly client for Elmish.Bridge
language
F#
tags
fsharp, fable, react, websocket, elmish, bridge
files
*.fsproj ==> fable
*.fs ==> fable

View File

@@ -0,0 +1,22 @@
// Auto-Generated by FAKE; do not edit
namespace System
open System.Reflection
open System.Runtime.CompilerServices
[<assembly: AssemblyTitleAttribute("Fable.SignalR.Elmish")>]
[<assembly: AssemblyProductAttribute("Fable.SignalR")>]
[<assembly: AssemblyDescriptionAttribute("Fable and server bindings for SignalR.")>]
[<assembly: AssemblyVersionAttribute("0.0.1")>]
[<assembly: AssemblyFileVersionAttribute("0.0.1")>]
[<assembly: AssemblyConfigurationAttribute("Release")>]
[<assembly: InternalsVisibleToAttribute("Fable.SignalR.Elmish.Tests")>]
do ()
module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "Fable.SignalR.Elmish"
let [<Literal>] AssemblyProduct = "Fable.SignalR"
let [<Literal>] AssemblyDescription = "Fable and server bindings for SignalR."
let [<Literal>] AssemblyVersion = "0.0.1"
let [<Literal>] AssemblyFileVersion = "0.0.1"
let [<Literal>] AssemblyConfiguration = "Release"
let [<Literal>] InternalsVisibleTo = "Fable.SignalR.Elmish.Tests"

View File

@@ -0,0 +1,77 @@
namespace Fable.SignalR
open Elmish
open Fable.Core
open System.ComponentModel
module Elmish =
type ElmishHub<'ClientApi,'ServerApi> [<EditorBrowsable(EditorBrowsableState.Never)>] (hub: HubConnection<'ClientApi,'ServerApi>) =
[<EditorBrowsable(EditorBrowsableState.Never)>]
member _.hub = hub
[<EditorBrowsable(EditorBrowsableState.Never)>]
member _.CancellationToken = new System.Threading.CancellationTokenSource()
member this.Dispose () =
this.hub.stopNow()
this.CancellationToken.Cancel()
this.CancellationToken.Dispose()
interface System.IDisposable with
member this.Dispose () = this.Dispose()
[<RequireQualifiedAccess>]
module Cmd =
[<RequireQualifiedAccess>]
module SignalR =
let inline connect
(registerHub: ElmishHub<'ClientApi,'ServerApi> -> 'Msg)
(registerMsgs: 'ServerApi -> 'Msg)
(config: HubConnectionBuilder<'ClientApi,'ServerApi> -> HubConnectionBuilder<'ClientApi,'ServerApi>) : Cmd<'Msg> =
[ fun dispatch ->
let connection = SignalR.connect(config)
connection.onMsg(registerMsgs >> dispatch)
connection.startNow()
registerHub (new ElmishHub<'ClientApi,'ServerApi>(connection))
|> dispatch ]
let inline connectWith
(registerHub: ElmishHub<'ClientApi,'ServerApi> -> 'Msg)
(registerMsgs: 'ServerApi -> 'Msg)
(config: HubConnectionBuilder<'ClientApi,'ServerApi> -> HubConnectionBuilder<'ClientApi,'ServerApi>)
(registerHandlers: HubRegistration -> ('Msg -> unit) -> unit) : Cmd<'Msg> =
[ fun dispatch ->
let connection =
SignalR.connect(config)
registerHandlers (connection :> HubRegistration) dispatch
connection.onMsg(registerMsgs >> dispatch)
connection.startNow()
registerHub (new ElmishHub<'ClientApi,'ServerApi>(connection))
|> dispatch ]
let invoke (hub: ElmishHub<'ClientApi,'Msg> option) (msg: 'ClientApi) (onError: exn -> 'Msg) =
match hub with
| Some hub ->
Cmd.OfAsyncWith.either
(fun msg -> Async.StartImmediate(msg, hub.CancellationToken.Token))
(fun msg -> hub.hub.invoke msg)
msg
id
onError
| None ->
#if DEBUG
JS.console.error("Cannot send a message if hub is not initialized!")
#endif
[ fun _ -> () ]
let send (hub: ElmishHub<'ClientApi,'ServerApi> option) (msg: 'ClientApi) : Cmd<_> =
[ fun _ -> hub |> Option.iter (fun hub -> hub.hub.sendNow msg) ]

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Elmish.fs" />
<None Include="paket.references" />
<None Include="paket.template" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Fable.SignalR\Fable.SignalR.fsproj" />
</ItemGroup>
<PropertyGroup>
<NpmDependencies>
<NpmPackage Name="jest" Version="gte 26 lt 27" ResolutionStrategy="max" DevDependency="true" />
<NpmPackage Name="@testing-library/jest-dom" Version="gte 5.7 lt 6" ResolutionStrategy="max" DevDependency="true" />
</NpmDependencies>
</PropertyGroup>
<Import Project="..\..\.paket\Paket.Restore.targets" />
</Project>

View File

@@ -0,0 +1,5 @@
group Fable.SignalR.Elmish
Fable.Core
Fable.Elmish
Fable.Promise
FSharp.Core

View File

@@ -0,0 +1,12 @@
type project
licenseExpression https://github.com/Shmew/Feliz.UseBridge/blob/master/LICENSE
authors Cody Johnson
description
A Feliz friendly client for Elmish.Bridge
language
F#
tags
fsharp, fable, react, websocket, elmish, bridge
files
*.fsproj ==> fable
*.fs ==> fable

View File

@@ -0,0 +1,22 @@
// Auto-Generated by FAKE; do not edit
namespace System
open System.Reflection
open System.Runtime.CompilerServices
[<assembly: AssemblyTitleAttribute("Fable.SignalR.Feliz")>]
[<assembly: AssemblyProductAttribute("Fable.SignalR")>]
[<assembly: AssemblyDescriptionAttribute("Fable and server bindings for SignalR.")>]
[<assembly: AssemblyVersionAttribute("0.0.1")>]
[<assembly: AssemblyFileVersionAttribute("0.0.1")>]
[<assembly: AssemblyConfigurationAttribute("Release")>]
[<assembly: InternalsVisibleToAttribute("Fable.SignalR.Feliz.Tests")>]
do ()
module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "Fable.SignalR.Feliz"
let [<Literal>] AssemblyProduct = "Fable.SignalR"
let [<Literal>] AssemblyDescription = "Fable and server bindings for SignalR."
let [<Literal>] AssemblyVersion = "0.0.1"
let [<Literal>] AssemblyFileVersion = "0.0.1"
let [<Literal>] AssemblyConfiguration = "Release"
let [<Literal>] InternalsVisibleTo = "Fable.SignalR.Feliz.Tests"

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Feliz.fs" />
<None Include="paket.references" />
<None Include="paket.template" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Fable.SignalR\Fable.SignalR.fsproj" />
</ItemGroup>
<PropertyGroup>
<NpmDependencies>
<NpmPackage Name="jest" Version="gte 26 lt 27" ResolutionStrategy="max" DevDependency="true" />
<NpmPackage Name="@testing-library/jest-dom" Version="gte 5.7 lt 6" ResolutionStrategy="max" DevDependency="true" />
</NpmDependencies>
</PropertyGroup>
<Import Project="..\..\.paket\Paket.Restore.targets" />
</Project>

Some files were not shown because too many files have changed in this diff Show More