Add project files.
14
.editorconfig
Normal 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
@@ -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
|
||||
20
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
vendored
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
488
.paket/Paket.Restore.targets
Normal 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)' > '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)' < '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)' < '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)`, `": "`).Length) > 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
0
Contributing.md
Normal file
208
Fable.SignalR.sln
Normal 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
@@ -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
@@ -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
@@ -0,0 +1,10 @@
|
||||
# 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
@@ -0,0 +1,2 @@
|
||||
### 0.0.1 - Wednesday, March 25, 2020
|
||||
* Initial build
|
||||
6
build.cmd
Normal file
@@ -0,0 +1,6 @@
|
||||
@echo off
|
||||
cls
|
||||
|
||||
dotnet restore build.proj
|
||||
|
||||
fake build %*
|
||||
491
build.fsx
Normal 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
@@ -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
@@ -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
@@ -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")
|
||||
22
demo/Client/AssemblyInfo.fs
Normal 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
@@ -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>
|
||||
15
demo/Client/paket.references
Normal 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
|
||||
BIN
demo/Client/public/Images/GEHA_logo.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
demo/Client/public/Images/favicon/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 392 B |
BIN
demo/Client/public/Images/favicon/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 817 B |
BIN
demo/Client/public/Images/favicon/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
12
demo/Client/public/index.html
Normal 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>
|
||||
31
demo/Client/scss/main.scss
Normal 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
@@ -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
|
||||
22
demo/Server/AssemblyInfo.fs
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
5
demo/Server/paket.references
Normal file
@@ -0,0 +1,5 @@
|
||||
group Server
|
||||
FSharp.Core
|
||||
FSharp.Control.AsyncSeq
|
||||
Saturn
|
||||
TaskBuilder.fs
|
||||
22
demo/Shared/AssemblyInfo.fs
Normal 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
@@ -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
@@ -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>
|
||||
2
demo/Shared/paket.references
Normal file
@@ -0,0 +1,2 @@
|
||||
group Shared
|
||||
FSharp.Core
|
||||
23
docs/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Fable.Jester [](https://www.nuget.org/packages/Fable.Jester) [](https://www.nuget.org/packages/Fable.ReactTestingLibrary) [](https://www.nuget.org/packages/Fable.FastCheck) [](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
@@ -0,0 +1,2 @@
|
||||
### 0.0.1 - Wednesday, March 25, 2020
|
||||
* Initial build
|
||||
3
docs/acknowledgment.md
Normal 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
@@ -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
@@ -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
@@ -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
|
||||
552
docs/fast-check/arbitrary/README.md
Normal 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>
|
||||
```
|
||||
107
docs/fast-check/arbitrary/array.md
Normal 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 [][]>
|
||||
```
|
||||
35
docs/fast-check/arbitrary/ce.md
Normal 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
|
||||
518
docs/fast-check/arbitrary/constrained-defaults.md
Normal 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>
|
||||
```
|
||||
596
docs/fast-check/arbitrary/defaults.md
Normal 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>
|
||||
```
|
||||
107
docs/fast-check/arbitrary/list.md
Normal 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>
|
||||
```
|
||||
23
docs/fast-check/arbitrary/map.md
Normal 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>>
|
||||
```
|
||||
108
docs/fast-check/arbitrary/resizearray.md
Normal 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>>>
|
||||
```
|
||||
107
docs/fast-check/arbitrary/seq.md
Normal 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>
|
||||
```
|
||||
22
docs/fast-check/arbitrary/set.md
Normal 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>>
|
||||
```
|
||||
91
docs/fast-check/elmish-model-testing.md
Normal 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
|
||||
658
docs/fast-check/fast-check.md
Normal 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
|
||||
```
|
||||
511
docs/fast-check/jest-extension.md
Normal 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)
|
||||
)
|
||||
```
|
||||
331
docs/fast-check/model-testing.md
Normal 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)
|
||||
))
|
||||
)
|
||||
```
|
||||
180
docs/fast-check/scheduler.md
Normal 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)
|
||||
}
|
||||
))
|
||||
)
|
||||
```
|
||||
BIN
docs/images/jest/sourcemap.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/images/jest/test-summary.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
docs/images/test.gif
Normal file
|
After Width: | Height: | Size: 255 KiB |
124
docs/index.html
Normal 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>
|
||||
14
docs/installation/fast-check-jest.md
Normal 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).
|
||||
35
docs/installation/fast-check.md
Normal 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
@@ -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
|
||||
49
docs/installation/react-testing-library.md
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
188
docs/rtl/queries-for-element.md
Normal 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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
|
||||
.Content {
|
||||
max-width: 1000px !important;
|
||||
}
|
||||
70
package.json
Normal 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
@@ -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
3
paket.references
Normal file
@@ -0,0 +1,3 @@
|
||||
group Tooling
|
||||
dotnet-fake
|
||||
dotnet-fable
|
||||
15
publish.js
Normal 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);
|
||||
}
|
||||
});
|
||||
167
src/Fable.SignalR.AspNetCore/AspNetCore.fs
Normal 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)
|
||||
22
src/Fable.SignalR.AspNetCore/AssemblyInfo.fs
Normal 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"
|
||||
19
src/Fable.SignalR.AspNetCore/Fable.SignalR.AspNetCore.fsproj
Normal 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>
|
||||
4
src/Fable.SignalR.AspNetCore/paket.references
Normal file
@@ -0,0 +1,4 @@
|
||||
group Fable.SignalR.AspNetCore
|
||||
Fable.Remoting.Json
|
||||
FSharp.Core
|
||||
Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson
|
||||
12
src/Fable.SignalR.AspNetCore/paket.template
Normal 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
|
||||
22
src/Fable.SignalR.Elmish/AssemblyInfo.fs
Normal 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"
|
||||
77
src/Fable.SignalR.Elmish/Elmish.fs
Normal 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) ]
|
||||
22
src/Fable.SignalR.Elmish/Fable.SignalR.Elmish.fsproj
Normal 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>
|
||||
5
src/Fable.SignalR.Elmish/paket.references
Normal file
@@ -0,0 +1,5 @@
|
||||
group Fable.SignalR.Elmish
|
||||
Fable.Core
|
||||
Fable.Elmish
|
||||
Fable.Promise
|
||||
FSharp.Core
|
||||
12
src/Fable.SignalR.Elmish/paket.template
Normal 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
|
||||
22
src/Fable.SignalR.Feliz/AssemblyInfo.fs
Normal 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"
|
||||
22
src/Fable.SignalR.Feliz/Fable.SignalR.Feliz.fsproj
Normal 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>
|
||||