This commit is contained in:
Wiesław Šoltés 2016-08-31 23:15:05 +02:00
Родитель 47c4d12acf
Коммит 00d63a6af3
25 изменённых файлов: 2157 добавлений и 1 удалений

63
.gitattributes поставляемый Normal file
Просмотреть файл

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.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
.travis.yml Normal file
Просмотреть файл

@ -0,0 +1,20 @@
language: csharp
os:
- linux
- osx
mono:
- latest
script:
- ./build.sh --target "Travis" --platform "Any CPU" --configuration "Release"
notifications:
email: false
webhooks:
urls:
- https://webhooks.gitter.im/e/190345f2f4350d168c39
on_success: change
on_failure: always
on_start: never
cache:
directories:
- packages
- tools

8
NuGet.Config Normal file
Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="avalonia-ci" value="https://www.myget.org/F/avalonia-ci/api/v2" />
</packageSources>
</configuration>

Просмотреть файл

@ -1,2 +1,67 @@
# ReactiveHistory
Reactive undo/redo framework for .NET.
[![Gitter](https://badges.gitter.im/wieslawsoltes/ReactiveHistory.svg)](https://gitter.im/wieslawsoltes/ReactiveHistory?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Build status](https://ci.appveyor.com/api/projects/status/9mgwu7obsuh89kys?svg=true)](https://ci.appveyor.com/project/wieslawsoltes/reactivehistory)
[![Build Status](https://travis-ci.org/wieslawsoltes/ReactiveHistory.svg?branch=master)](https://travis-ci.org/wieslawsoltes/ReactiveHistory)
[![NuGet](https://img.shields.io/nuget/v/ReactiveHistory.svg)](https://www.nuget.org/packages/ReactiveHistory) [![MyGet](https://img.shields.io/myget/reactivehistory-nightly/vpre/ReactiveHistory.svg?label=myget)](https://www.myget.org/gallery/reactivehistory-nightly)
**ReactiveHistory** is an undo/redo framework for .NET.
## Building ReactiveHistory
First, clone the repository or download the latest zip.
```
git clone https://github.com/wieslawsoltes/ReactiveHistory.git
git submodule update --init --recursive
```
### Build using IDE
* [Visual Studio Community 2015](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) for `Windows` builds.
* [MonoDevelop](http://www.monodevelop.com/) for `Linux` builds.
Open `ReactiveHistory.sln` in selected IDE and run `Build` command.
### Build on Windows using script
Open up a Powershell prompt and execute the bootstrapper script:
```PowerShell
PS> .\build.ps1 -Target "Default" -Platform "Any CPU" -Configuration "Release"
```
### Build on Linux/OSX using script
Open up a terminal prompt and execute the bootstrapper script:
```Bash
$ ./build.sh --target "Default" --platform "Any CPU" --configuration "Release"
```
## NuGet
ReactiveHistory is delivered as a NuGet package.
You can find the packages here [NuGet](https://www.nuget.org/packages/ReactiveHistory/) or by using nightly build feed:
* Add `https://www.myget.org/F/reactivehistory-nightly/api/v2` to your package sources
* Update your package using `ReactiveHistory` feed
You can install the package like this:
`Install-Package ReactiveHistory -Pre`
### Package Dependencies
* System.Reactive
* System.Reactive.Core
* System.Reactive.Interfaces
* System.Reactive.Linq
* System.Reactive.PlatformServices
### Package Sources
* https://api.nuget.org/v3/index.json
## License
ReactiveHistory is licensed under the [MIT license](LICENSE.TXT).

48
ReactiveHistory.sln Normal file
Просмотреть файл

@ -0,0 +1,48 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveHistory", "src\ReactiveHistory\ReactiveHistory.csproj", "{9832484C-4BBE-43E9-B40E-9F57AF68A185}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{15FC57FA-8C2F-43AF-82D4-F53A1AA3A7EF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{E982B451-CC08-4731-B035-E0E1B38B81A5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{A47CBA6E-2F46-4308-BA5B-DE4AE5253F32}"
ProjectSection(SolutionItems) = preProject
.travis.yml = .travis.yml
appveyor.yml = appveyor.yml
build.cake = build.cake
build.ps1 = build.ps1
build.sh = build.sh
NuGet.Config = NuGet.Config
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveHistory.UnitTests", "tests\ReactiveHistory.UnitTests\ReactiveHistory.UnitTests.csproj", "{71BA2D95-53E6-42D4-ADAA-CF34C77494F5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{4A600469-8200-4BDD-AA90-6B34B2D1CD2A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9832484C-4BBE-43E9-B40E-9F57AF68A185}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9832484C-4BBE-43E9-B40E-9F57AF68A185}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9832484C-4BBE-43E9-B40E-9F57AF68A185}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9832484C-4BBE-43E9-B40E-9F57AF68A185}.Release|Any CPU.Build.0 = Release|Any CPU
{71BA2D95-53E6-42D4-ADAA-CF34C77494F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71BA2D95-53E6-42D4-ADAA-CF34C77494F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71BA2D95-53E6-42D4-ADAA-CF34C77494F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71BA2D95-53E6-42D4-ADAA-CF34C77494F5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9832484C-4BBE-43E9-B40E-9F57AF68A185} = {15FC57FA-8C2F-43AF-82D4-F53A1AA3A7EF}
{71BA2D95-53E6-42D4-ADAA-CF34C77494F5} = {E982B451-CC08-4731-B035-E0E1B38B81A5}
EndGlobalSection
EndGlobal

27
appveyor.yml Normal file
Просмотреть файл

@ -0,0 +1,27 @@
image: Visual Studio 2015
platform:
- Any CPU
configuration:
- Release
environment:
NUGET_API_KEY:
secure: iSkm19DNdKOLxxCvHiaKikWXy25ZMwGBKpv+EgEprliGl0qX5HtKohLmwJsLnx5O
NUGET_API_URL: https://www.nuget.org/api/v2/package
MYGET_API_KEY:
secure: xhYvrWYPHdNI+mQD+f4Zu3baL7fqW+nO/hYXJdN070Lej8wpa6gfrLi1NGtnQlAs
MYGET_API_URL: https://www.myget.org/F/reactivehistory-nightly/api/v2/package
before_build:
- cmd: git submodule update --init
build_script:
- ps: .\build.ps1 -Target "AppVeyor" -Platform "$env:platform" -Configuration "$env:configuration"
test: off
notifications:
- provider: Webhook
url: https://webhooks.gitter.im/e/190345f2f4350d168c39
method: POST
on_build_success: true
on_build_failure: true
on_build_status_changed: true
cache:
- packages -> src\**\packages.config, tests\**\packages.config
- tools -> build.cake, tools\packages.config

423
build.cake Normal file
Просмотреть файл

@ -0,0 +1,423 @@
///////////////////////////////////////////////////////////////////////////////
// ADDINS
///////////////////////////////////////////////////////////////////////////////
#addin "nuget:?package=Polly&version=4.2.0"
#addin "nuget:?package=NuGet.Core&version=2.12.0"
///////////////////////////////////////////////////////////////////////////////
// TOOLS
///////////////////////////////////////////////////////////////////////////////
#tool "nuget:?package=xunit.runner.console&version=2.1.0"
///////////////////////////////////////////////////////////////////////////////
// USINGS
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using Polly;
using NuGet;
///////////////////////////////////////////////////////////////////////////////
// ARGUMENTS
///////////////////////////////////////////////////////////////////////////////
var target = Argument("target", "Default");
var platform = Argument("platform", "Any CPU");
var configuration = Argument("configuration", "Release");
///////////////////////////////////////////////////////////////////////////////
// CONFIGURATION
///////////////////////////////////////////////////////////////////////////////
var MainRepo = "wieslawsoltes/ReactiveHistory";
var MasterBranch = "master";
var AssemblyInfoPath = File("./src/ReactiveHistory/Properties/AssemblyInfo.cs");
var ReleasePlatform = "Any CPU";
var ReleaseConfiguration = "Release";
var MSBuildSolution = "./ReactiveHistory.sln";
var XBuildSolution = "./ReactiveHistory.mono.sln";
///////////////////////////////////////////////////////////////////////////////
// PARAMETERS
///////////////////////////////////////////////////////////////////////////////
var isPlatformAnyCPU = StringComparer.OrdinalIgnoreCase.Equals(platform, "Any CPU");
var isPlatformX86 = StringComparer.OrdinalIgnoreCase.Equals(platform, "x86");
var isPlatformX64 = StringComparer.OrdinalIgnoreCase.Equals(platform, "x64");
var isLocalBuild = BuildSystem.IsLocalBuild;
var isRunningOnUnix = IsRunningOnUnix();
var isRunningOnWindows = IsRunningOnWindows();
var isRunningOnAppVeyor = BuildSystem.AppVeyor.IsRunningOnAppVeyor;
var isPullRequest = BuildSystem.AppVeyor.Environment.PullRequest.IsPullRequest;
var isMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, BuildSystem.AppVeyor.Environment.Repository.Name);
var isMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, BuildSystem.AppVeyor.Environment.Repository.Branch);
var isTagged = BuildSystem.AppVeyor.Environment.Repository.Tag.IsTag
&& !string.IsNullOrWhiteSpace(BuildSystem.AppVeyor.Environment.Repository.Tag.Name);
var isReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleasePlatform, platform)
&& StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, configuration);
var isMyGetRelease = !isTagged && isReleasable;
var isNuGetRelease = isTagged && isReleasable;
///////////////////////////////////////////////////////////////////////////////
// VERSION
///////////////////////////////////////////////////////////////////////////////
var version = ParseAssemblyInfo(AssemblyInfoPath).AssemblyVersion;
if (isRunningOnAppVeyor)
{
if (isTagged)
{
// Use Tag Name as version
version = BuildSystem.AppVeyor.Environment.Repository.Tag.Name;
}
else
{
// Use AssemblyVersion with Build as version
version += "-build" + EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-alpha";
}
}
///////////////////////////////////////////////////////////////////////////////
// DIRECTORIES
///////////////////////////////////////////////////////////////////////////////
var artifactsDir = (DirectoryPath)Directory("./artifacts");
var testResultsDir = artifactsDir.Combine("test-results");
var nugetRoot = artifactsDir.Combine("nuget");
var dirSuffix = isPlatformAnyCPU ? configuration : platform + "/" + configuration;
var buildDirs =
GetDirectories("./src/**/bin/" + dirSuffix) +
GetDirectories("./src/**/obj/" + dirSuffix) +
GetDirectories("./samples/**/bin/" + dirSuffix) +
GetDirectories("./samples/**/obj/" + dirSuffix) +
GetDirectories("./tests/**/bin/" + dirSuffix) +
GetDirectories("./tests/**/obj/" + dirSuffix);
///////////////////////////////////////////////////////////////////////////////
// NUGET NUSPECS
///////////////////////////////////////////////////////////////////////////////
// Key: Package Id
// Value is Tuple where Item1: Package Version, Item2: The packages.config file path.
var packageVersions = new Dictionary<string, IList<Tuple<string,string>>>();
System.IO.Directory.EnumerateFiles(((DirectoryPath)Directory("./src")).FullPath, "packages.config", SearchOption.AllDirectories).ToList().ForEach(fileName =>
{
var file = new PackageReferenceFile(fileName);
foreach (PackageReference packageReference in file.GetPackageReferences())
{
IList<Tuple<string, string>> versions;
packageVersions.TryGetValue(packageReference.Id, out versions);
if (versions == null)
{
versions = new List<Tuple<string, string>>();
packageVersions[packageReference.Id] = versions;
}
versions.Add(Tuple.Create(packageReference.Version.ToString(), fileName));
}
});
Information("Checking installed NuGet package dependencies versions:");
packageVersions.ToList().ForEach(package =>
{
var packageVersion = package.Value.First().Item1;
bool isValidVersion = package.Value.All(x => x.Item1 == packageVersion);
if (!isValidVersion)
{
Information("Error: package {0} has multiple versions installed:", package.Key);
foreach (var v in package.Value)
{
Information("{0}, file: {1}", v.Item1, v.Item2);
}
throw new Exception("Detected multiple NuGet package version installed for different projects.");
}
});
Information("Setting NuGet package dependencies versions:");
var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
var nuspecNuGetHistory = new NuGetPackSettings()
{
Id = "ReactiveHistory",
Version = version,
Authors = new [] { "wieslaw.soltes" },
Owners = new [] { "wieslaw.soltes" },
LicenseUrl = new Uri("http://opensource.org/licenses/MIT"),
ProjectUrl = new Uri("https://github.com/wieslawsoltes/ReactiveHistory/"),
RequireLicenseAcceptance = false,
Symbols = false,
NoPackageAnalysis = true,
Description = "Reactive undo/redo framework for .NET.",
Copyright = "Copyright 2016",
Tags = new [] { "Undo", "Redo", "History", "Reactive", "Managed", "C#" },
Dependencies = new []
{
new NuSpecDependency { Id = "System.Reactive", Version = SystemReactiveVersion }
},
Files = new []
{
new NuSpecContent { Source = "src/ReactiveHistory/bin/" + dirSuffix + "/ReactiveHistory.dll", Target = "lib/portable-windows8+net45" },
new NuSpecContent { Source = "src/ReactiveHistory/bin/" + dirSuffix + "/ReactiveHistory.xml", Target = "lib/portable-windows8+net45" }
},
BasePath = Directory("./"),
OutputDirectory = nugetRoot
};
var nuspecNuGetSettings = new List<NuGetPackSettings>();
nuspecNuGetSettings.Add(nuspecNuGetHistory);
var nugetPackages = nuspecNuGetSettings.Select(nuspec => {
return nuspec.OutputDirectory.CombineWithFilePath(string.Concat(nuspec.Id, ".", nuspec.Version, ".nupkg"));
}).ToArray();
///////////////////////////////////////////////////////////////////////////////
// INFORMATION
///////////////////////////////////////////////////////////////////////////////
Information("Building version {0} of ReactiveHistory ({1}, {2}, {3}) using version {4} of Cake.",
version,
platform,
configuration,
target,
typeof(ICakeContext).Assembly.GetName().Version.ToString());
if (isRunningOnAppVeyor)
{
Information("Repository Name: " + BuildSystem.AppVeyor.Environment.Repository.Name);
Information("Repository Branch: " + BuildSystem.AppVeyor.Environment.Repository.Branch);
}
Information("Target: " + target);
Information("Platform: " + platform);
Information("Configuration: " + configuration);
Information("IsLocalBuild: " + isLocalBuild);
Information("IsRunningOnUnix: " + isRunningOnUnix);
Information("IsRunningOnWindows: " + isRunningOnWindows);
Information("IsRunningOnAppVeyor: " + isRunningOnAppVeyor);
Information("IsPullRequest: " + isPullRequest);
Information("IsMainRepo: " + isMainRepo);
Information("IsMasterBranch: " + isMasterBranch);
Information("IsTagged: " + isTagged);
Information("IsReleasable: " + isReleasable);
Information("IsMyGetRelease: " + isMyGetRelease);
Information("IsNuGetRelease: " + isNuGetRelease);
///////////////////////////////////////////////////////////////////////////////
// TASKS
///////////////////////////////////////////////////////////////////////////////
Task("Clean")
.Does(() =>
{
CleanDirectories(buildDirs);
CleanDirectory(artifactsDir);
CleanDirectory(testResultsDir);
CleanDirectory(nugetRoot);
});
Task("Restore-NuGet-Packages")
.IsDependentOn("Clean")
.Does(() =>
{
var maxRetryCount = 5;
var toolTimeout = 1d;
Policy
.Handle<Exception>()
.Retry(maxRetryCount, (exception, retryCount, context) => {
if (retryCount == maxRetryCount)
{
throw exception;
}
else
{
Verbose("{0}", exception);
toolTimeout+=0.5;
}})
.Execute(()=> {
if(isRunningOnWindows)
{
NuGetRestore(MSBuildSolution, new NuGetRestoreSettings {
ToolTimeout = TimeSpan.FromMinutes(toolTimeout)
});
}
else
{
NuGetRestore(XBuildSolution, new NuGetRestoreSettings {
ToolTimeout = TimeSpan.FromMinutes(toolTimeout)
});
}
});
});
Task("Build")
.IsDependentOn("Restore-NuGet-Packages")
.Does(() =>
{
if(isRunningOnWindows)
{
MSBuild(MSBuildSolution, settings => {
settings.SetConfiguration(configuration);
settings.WithProperty("Platform", "\"" + platform + "\"");
settings.SetVerbosity(Verbosity.Minimal);
});
}
else
{
XBuild(XBuildSolution, settings => {
settings.SetConfiguration(configuration);
settings.WithProperty("Platform", "\"" + platform + "\"");
settings.SetVerbosity(Verbosity.Minimal);
});
}
});
Task("Run-Unit-Tests")
.IsDependentOn("Build")
.Does(() =>
{
string pattern = "./tests/**/bin/" + dirSuffix + "/*.UnitTests.dll";
if (isPlatformAnyCPU || isPlatformX86)
{
XUnit2(pattern, new XUnit2Settings {
ToolPath = "./tools/xunit.runner.console/tools/xunit.console.x86.exe",
OutputDirectory = testResultsDir,
XmlReportV1 = true,
NoAppDomain = true
});
}
else
{
XUnit2(pattern, new XUnit2Settings {
ToolPath = "./tools/xunit.runner.console/tools/xunit.console.exe",
OutputDirectory = testResultsDir,
XmlReportV1 = true,
NoAppDomain = true
});
}
});
Task("Create-NuGet-Packages")
.IsDependentOn("Run-Unit-Tests")
.Does(() =>
{
foreach(var nuspec in nuspecNuGetSettings)
{
NuGetPack(nuspec);
}
});
Task("Upload-AppVeyor-Artifacts")
.IsDependentOn("Create-NuGet-Packages")
.WithCriteria(() => isRunningOnAppVeyor)
.Does(() =>
{
foreach(var nupkg in nugetPackages)
{
AppVeyor.UploadArtifact(nupkg.FullPath);
}
});
Task("Publish-MyGet")
.IsDependentOn("Create-NuGet-Packages")
.WithCriteria(() => !isLocalBuild)
.WithCriteria(() => !isPullRequest)
.WithCriteria(() => isMainRepo)
.WithCriteria(() => isMasterBranch)
.WithCriteria(() => isMyGetRelease)
.Does(() =>
{
var apiKey = EnvironmentVariable("MYGET_API_KEY");
if(string.IsNullOrEmpty(apiKey))
{
throw new InvalidOperationException("Could not resolve MyGet API key.");
}
var apiUrl = EnvironmentVariable("MYGET_API_URL");
if(string.IsNullOrEmpty(apiUrl))
{
throw new InvalidOperationException("Could not resolve MyGet API url.");
}
foreach(var nupkg in nugetPackages)
{
NuGetPush(nupkg, new NuGetPushSettings {
Source = apiUrl,
ApiKey = apiKey
});
}
})
.OnError(exception =>
{
Information("Publish-MyGet Task failed, but continuing with next Task...");
});
Task("Publish-NuGet")
.IsDependentOn("Create-NuGet-Packages")
.WithCriteria(() => !isLocalBuild)
.WithCriteria(() => !isPullRequest)
.WithCriteria(() => isMainRepo)
.WithCriteria(() => isMasterBranch)
.WithCriteria(() => isNuGetRelease)
.Does(() =>
{
var apiKey = EnvironmentVariable("NUGET_API_KEY");
if(string.IsNullOrEmpty(apiKey))
{
throw new InvalidOperationException("Could not resolve NuGet API key.");
}
var apiUrl = EnvironmentVariable("NUGET_API_URL");
if(string.IsNullOrEmpty(apiUrl))
{
throw new InvalidOperationException("Could not resolve NuGet API url.");
}
foreach(var nupkg in nugetPackages)
{
NuGetPush(nupkg, new NuGetPushSettings {
ApiKey = apiKey,
Source = apiUrl
});
}
})
.OnError(exception =>
{
Information("Publish-NuGet Task failed, but continuing with next Task...");
});
///////////////////////////////////////////////////////////////////////////////
// TARGETS
///////////////////////////////////////////////////////////////////////////////
Task("Package")
.IsDependentOn("Create-NuGet-Packages");
Task("Default")
.IsDependentOn("Package");
Task("AppVeyor")
.IsDependentOn("Upload-AppVeyor-Artifacts")
.IsDependentOn("Publish-MyGet")
.IsDependentOn("Publish-NuGet");
Task("Travis")
.IsDependentOn("Run-Unit-Tests");
///////////////////////////////////////////////////////////////////////////////
// EXECUTE
///////////////////////////////////////////////////////////////////////////////
RunTarget(target);

193
build.ps1 Normal file
Просмотреть файл

@ -0,0 +1,193 @@
##########################################################################
# This is the Cake bootstrapper script for PowerShell.
# This file was downloaded from https://github.com/cake-build/resources
# Feel free to change this file to fit your needs.
##########################################################################
<#
.SYNOPSIS
This is a Powershell script to bootstrap a Cake build.
.DESCRIPTION
This Powershell script will download NuGet if missing, restore NuGet tools (including Cake)
and execute your Cake build script with the parameters you provide.
.PARAMETER Script
The build script to execute.
.PARAMETER Target
The build script target to run.
.PARAMETER Platform
The build platform to use.
.PARAMETER Configuration
The build configuration to use.
.PARAMETER Verbosity
Specifies the amount of information to be displayed.
.PARAMETER Experimental
Tells Cake to use the latest Roslyn release.
.PARAMETER WhatIf
Performs a dry run of the build script.
No tasks will be executed.
.PARAMETER Mono
Tells Cake to use the Mono scripting engine.
.PARAMETER SkipToolPackageRestore
Skips restoring of packages.
.PARAMETER ScriptArgs
Remaining arguments are added here.
.LINK
http://cakebuild.net
#>
[CmdletBinding()]
Param(
[string]$Script = "build.cake",
[string]$Target = "Default",
[ValidateSet("Any CPU", "x86", "x64")]
[string]$Platform = "Any CPU",
[ValidateSet("Release", "Debug")]
[string]$Configuration = "Release",
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
[string]$Verbosity = "Verbose",
[switch]$Experimental,
[Alias("DryRun","Noop")]
[switch]$WhatIf,
[switch]$Mono,
[switch]$SkipToolPackageRestore,
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
[string[]]$ScriptArgs
)
[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
function MD5HashFile([string] $filePath)
{
if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf))
{
return $null
}
[System.IO.Stream] $file = $null;
[System.Security.Cryptography.MD5] $md5 = $null;
try
{
$md5 = [System.Security.Cryptography.MD5]::Create()
$file = [System.IO.File]::OpenRead($filePath)
return [System.BitConverter]::ToString($md5.ComputeHash($file))
}
finally
{
if ($file -ne $null)
{
$file.Dispose()
}
}
}
Write-Host "Preparing to run build script..."
if(!$PSScriptRoot){
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
}
$TOOLS_DIR = Join-Path $PSScriptRoot "tools"
$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe"
$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe"
$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config"
$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum"
# Should we use mono?
$UseMono = "";
if($Mono.IsPresent) {
Write-Verbose -Message "Using the Mono based scripting engine."
$UseMono = "-mono"
}
# Should we use the new Roslyn?
$UseExperimental = "";
if($Experimental.IsPresent -and !($Mono.IsPresent)) {
Write-Verbose -Message "Using experimental version of Roslyn."
$UseExperimental = "-experimental"
}
# Is this a dry run?
$UseDryRun = "";
if($WhatIf.IsPresent) {
$UseDryRun = "-dryrun"
}
# Make sure tools folder exists
if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) {
Write-Verbose -Message "Creating tools directory..."
New-Item -Path $TOOLS_DIR -Type directory | out-null
}
# Make sure that packages.config exist.
if (!(Test-Path $PACKAGES_CONFIG)) {
Write-Verbose -Message "Downloading packages.config..."
try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch {
Throw "Could not download packages.config."
}
}
# Try find NuGet.exe in path if not exists
if (!(Test-Path $NUGET_EXE)) {
Write-Verbose -Message "Trying to find nuget.exe in PATH..."
$existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) }
$NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1
if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) {
Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)."
$NUGET_EXE = $NUGET_EXE_IN_PATH.FullName
}
}
# Try download NuGet.exe if not exists
if (!(Test-Path $NUGET_EXE)) {
Write-Verbose -Message "Downloading NuGet.exe..."
try {
(New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE)
} catch {
Throw "Could not download NuGet.exe."
}
}
# Save nuget.exe path to environment to be available to child processed
$ENV:NUGET_EXE = $NUGET_EXE
# Restore tools from NuGet?
if(-Not $SkipToolPackageRestore.IsPresent) {
Push-Location
Set-Location $TOOLS_DIR
# Check for changes in packages.config and remove installed tools if true.
[string] $md5Hash = MD5HashFile($PACKAGES_CONFIG)
if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or
($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) {
Write-Verbose -Message "Missing or changed package.config hash..."
Remove-Item * -Recurse -Exclude packages.config,nuget.exe
}
Write-Verbose -Message "Restoring tools from NuGet..."
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
if ($LASTEXITCODE -ne 0) {
Throw "An error occured while restoring NuGet tools."
}
else
{
$md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII"
}
Write-Verbose -Message ($NuGetOutput | out-string)
Pop-Location
}
# Make sure that Cake has been installed.
if (!(Test-Path $CAKE_EXE)) {
Throw "Could not find Cake.exe at $CAKE_EXE"
}
# Start Cake
Write-Host "Running build script..."
Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -platform=`"$Platform`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs"
exit $LASTEXITCODE

103
build.sh Normal file
Просмотреть файл

@ -0,0 +1,103 @@
#!/usr/bin/env bash
##########################################################################
# This is the Cake bootstrapper script for Linux and OS X.
# This file was downloaded from https://github.com/cake-build/resources
# Feel free to change this file to fit your needs.
##########################################################################
# Define directories.
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
TOOLS_DIR=$SCRIPT_DIR/tools
NUGET_EXE=$TOOLS_DIR/nuget.exe
CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe
PACKAGES_CONFIG=$TOOLS_DIR/packages.config
PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum
# Define md5sum or md5 depending on Linux/OSX
MD5_EXE=
if [[ "$(uname -s)" == "Darwin" ]]; then
MD5_EXE="md5 -r"
else
MD5_EXE="md5sum"
fi
# Define default arguments.
SCRIPT="build.cake"
TARGET="Default"
CONFIGURATION="Release"
PLATFORM="Any CPU"
VERBOSITY="verbose"
DRYRUN=
SHOW_VERSION=false
SCRIPT_ARGUMENTS=()
# Parse arguments.
for i in "$@"; do
case $1 in
-s|--script) SCRIPT="$2"; shift ;;
-t|--target) TARGET="$2"; shift ;;
-p|--platform) PLATFORM="$2"; shift ;;
-c|--configuration) CONFIGURATION="$2"; shift ;;
-v|--verbosity) VERBOSITY="$2"; shift ;;
-d|--dryrun) DRYRUN="-dryrun" ;;
--version) SHOW_VERSION=true ;;
--) shift; SCRIPT_ARGUMENTS+=("$@"); break ;;
*) SCRIPT_ARGUMENTS+=("$1") ;;
esac
shift
done
# Make sure the tools folder exist.
if [ ! -d "$TOOLS_DIR" ]; then
mkdir "$TOOLS_DIR"
fi
# Make sure that packages.config exist.
if [ ! -f "$TOOLS_DIR/packages.config" ]; then
echo "Downloading packages.config..."
curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages
if [ $? -ne 0 ]; then
echo "An error occured while downloading packages.config."
exit 1
fi
fi
# Download NuGet if it does not exist.
if [ ! -f "$NUGET_EXE" ]; then
echo "Downloading NuGet..."
curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
if [ $? -ne 0 ]; then
echo "An error occured while downloading nuget.exe."
exit 1
fi
fi
# Restore tools from NuGet.
pushd "$TOOLS_DIR" >/dev/null
if [ ! -f $PACKAGES_CONFIG_MD5 ] || [ "$( cat $PACKAGES_CONFIG_MD5 | sed 's/\r$//' )" != "$( $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' )" ]; then
find . -type d ! -name . | xargs rm -rf
fi
mono "$NUGET_EXE" install -ExcludeVersion
if [ $? -ne 0 ]; then
echo "Could not restore NuGet packages."
exit 1
fi
$MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' >| $PACKAGES_CONFIG_MD5
popd >/dev/null
# Make sure that Cake has been installed.
if [ ! -f "$CAKE_EXE" ]; then
echo "Could not find Cake.exe at '$CAKE_EXE'."
exit 1
fi
# Start Cake
if $SHOW_VERSION; then
exec mono "$CAKE_EXE" -version
else
exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -platform="$PLATFORM" -configuration="$CONFIGURATION" -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}"
fi

Просмотреть файл

@ -0,0 +1,56 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
namespace ReactiveHistory
{
/// <summary>
/// Undo/redo action history contract.
/// </summary>
public interface IHistory
{
/// <summary>
/// Gets or sets flag indicating whether history is paused.
/// </summary>
bool IsPaused { get; set; }
/// <summary>
/// Gets or sets flag indicating whether undo action can execute.
/// </summary>
IObservable<bool> CanUndo { get; }
/// <summary>
/// Gets or sets flag indicating whether redo action can execute.
/// </summary>
IObservable<bool> CanRedo { get; }
/// <summary>
/// Gets or sets flag indicating whether clear action can execute.
/// </summary>
IObservable<bool> CanClear { get; }
/// <summary>
/// Makes undo/redo history snapshot.
/// </summary>
/// <param name="undo">The undo state action.</param>
/// <param name="redo">The redo state action.</param>
void Snapshot(Action undo, Action redo);
/// <summary>
/// Executes undo action.
/// </summary>
/// <returns>True if undo action was executed.</returns>
bool Undo();
/// <summary>
/// Executes redo action.
/// </summary>
/// <returns>True if redo action was executed.</returns>
bool Redo();
/// <summary>
/// Clears undo/redo actions history.
/// </summary>
void Clear();
}
}

Просмотреть файл

@ -0,0 +1,49 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Reactive.Linq;
namespace ReactiveHistory
{
/// <summary>
/// Observable extension methods for the generic <see cref="IObservable{T}"/> implementations.
/// </summary>
public static class ObservableHistoryExtensions
{
/// <summary>
/// Observe property changes with history.
/// </summary>
/// <param name="source">The property value observable.</param>
/// <param name="update">The property update action.</param>
/// <param name="currentValue">The property current value.</param>
/// <param name="history">The history object.</param>
/// <returns>The property value changes subscription.</returns>
public static IDisposable ObserveWithHistory<T>(this IObservable<T> source, Action<T> update, T currentValue, IHistory history)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (update == null)
throw new ArgumentNullException(nameof(update));
if (history == null)
throw new ArgumentNullException(nameof(history));
T previous = currentValue;
return source.Skip(1).Subscribe(
next =>
{
if (!history.IsPaused)
{
T undoValue = previous;
T redoValue = next;
Action undo = () => update(undoValue);
Action redo = () => update(redoValue);
history.Snapshot(undo, redo);
}
previous = next;
});
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("ReactiveHistory")]
[assembly: AssemblyDescription("Reactive undo/redo framework for .NET.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Wiesław Šoltés")]
[assembly: AssemblyProduct("ReactiveHistory")]
[assembly: AssemblyCopyright("Copyright © Wiesław Šoltés 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
[assembly: InternalsVisibleTo("ReactiveHistory.UnitTests")]
[assembly: AssemblyVersion("0.1.0")]
[assembly: AssemblyFileVersion("0.1.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]

Просмотреть файл

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{9832484C-4BBE-43E9-B40E-9F57AF68A185}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ReactiveHistory</RootNamespace>
<AssemblyName>ReactiveHistory</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="IHistory.cs" />
<Compile Include="ObservableHistoryExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StackHistory.cs" />
<Compile Include="StackHistoryExtensions.cs" />
<Compile Include="State.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Reactive.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reactive.Core.3.0.0\lib\netstandard1.1\System.Reactive.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Interfaces, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reactive.Interfaces.3.0.0\lib\netstandard1.0\System.Reactive.Interfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Linq, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reactive.Linq.3.0.0\lib\netstandard1.1\System.Reactive.Linq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.PlatformServices, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reactive.PlatformServices.3.0.0\lib\netstandard1.0\System.Reactive.PlatformServices.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -0,0 +1,140 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Reactive.Subjects;
namespace ReactiveHistory
{
/// <summary>
/// Undo/redo stack based action history.
/// </summary>
public class StackHistory : IHistory
{
private readonly Subject<bool> _canUndo;
private readonly Subject<bool> _canRedo;
private readonly Subject<bool> _canClear;
private volatile bool _isPaused;
/// <summary>
/// Gets or sets undo states stack.
/// </summary>
public Stack<State> Undos { get; set; }
/// <summary>
/// Gets or sets redo states stack.
/// </summary>
public Stack<State> Redos { get; set; }
/// <inheritdoc/>
public bool IsPaused
{
get { return _isPaused; }
set { _isPaused = value; }
}
/// <inheritdoc/>
public IObservable<bool> CanUndo
{
get { return _canUndo.AsObservable(); }
}
/// <inheritdoc/>
public IObservable<bool> CanRedo
{
get { return _canRedo.AsObservable(); }
}
/// <inheritdoc/>
public IObservable<bool> CanClear
{
get { return _canClear.AsObservable(); }
}
/// <summary>
/// Initializes a new <see cref="StackHistory"/> instance.
/// </summary>
public StackHistory()
{
Undos = new Stack<State>();
Redos = new Stack<State>();
_isPaused = false;
_canUndo = new Subject<bool>();
_canRedo = new Subject<bool>();
_canClear = new Subject<bool>();
}
/// <inheritdoc/>
public void Snapshot(Action undo, Action redo)
{
if (undo == null)
throw new ArgumentNullException(nameof(undo));
if (redo == null)
throw new ArgumentNullException(nameof(redo));
if (Redos.Count > 0)
{
Redos.Clear();
_canRedo.OnNext(false);
}
Undos.Push(new State(undo, redo));
_canUndo.OnNext(true);
_canClear.OnNext(true);
}
/// <inheritdoc/>
public bool Undo()
{
if (Undos.Count > 0)
{
IsPaused = true;
var state = Undos.Pop();
if (Undos.Count == 0)
{
_canUndo.OnNext(false);
}
state.Undo.Invoke();
Redos.Push(state);
_canRedo.OnNext(true);
_canClear.OnNext(true);
IsPaused = false;
return true;
}
return false;
}
/// <inheritdoc/>
public bool Redo()
{
if (Redos.Count > 0)
{
IsPaused = true;
var state = Redos.Pop();
if (Redos.Count == 0)
{
_canRedo.OnNext(false);
}
state.Redo.Invoke();
Undos.Push(state);
_canUndo.OnNext(true);
_canClear.OnNext(true);
IsPaused = false;
return true;
}
return false;
}
/// <inheritdoc/>
public void Clear()
{
Undos.Clear();
Redos.Clear();
_canUndo.OnNext(false);
_canRedo.OnNext(false);
_canClear.OnNext(false);
}
}
}

Просмотреть файл

@ -0,0 +1,161 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace ReactiveHistory
{
/// <summary>
/// Stack history extension methods for the generic <see cref="IList{T}"/> implementations.
/// </summary>
public static class StackHistoryExtensions
{
/// <summary>
/// Adds item to the source list with history.
/// </summary>
/// <typeparam name="T">The item type.</typeparam>
/// <param name="source">The source list.</param>
/// <param name="item">The item to add.</param>
/// <param name="history">The history object.</param>
public static void CreateWithHistory<T>(this IList<T> source, T item, IHistory history)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (item == null)
throw new ArgumentNullException(nameof(item));
if (history == null)
throw new ArgumentNullException(nameof(history));
if (!history.IsPaused)
{
int index = source.Count;
Action redo = () => source.Insert(index, item);
Action undo = () => source.RemoveAt(index);
history.Snapshot(undo, redo);
redo.Invoke();
}
}
/// <summary>
/// Inserts item to the source list with history.
/// </summary>
/// <typeparam name="T">The item type.</typeparam>
/// <param name="source">The source list.</param>
/// <param name="index">The item insertion index.</param>
/// <param name="item">The item to insert.</param>
/// <param name="history">The history object.</param>
public static void CreateWithHistory<T>(this IList<T> source, int index, T item, IHistory history)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (index < 0)
throw new IndexOutOfRangeException("Index can not be negative.");
if (item == null)
throw new ArgumentNullException(nameof(item));
if (history == null)
throw new ArgumentNullException(nameof(history));
if (!history.IsPaused)
{
Action redo = () => source.Insert(index, item);
Action undo = () => source.RemoveAt(index);
history.Snapshot(undo, redo);
redo.Invoke();
}
}
/// <summary>
/// Replaces item at specified index in the source list with history.
/// </summary>
/// <typeparam name="T">The item type.</typeparam>
/// <param name="source">The source list.</param>
/// <param name="index">The item index to replace.</param>
/// <param name="item">The replaced item.</param>
/// <param name="history">The history object.</param>
public static void UpdateWithHistory<T>(this IList<T> source, int index, T item, IHistory history)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (index < 0)
throw new IndexOutOfRangeException("Index can not be negative.");
if (item == null)
throw new ArgumentNullException(nameof(item));
if (history == null)
throw new ArgumentNullException(nameof(history));
if (!history.IsPaused)
{
T oldValue = source[index];
T newValue = item;
Action redo = () => source[index] = newValue;
Action undo = () => source[index] = oldValue;
history.Snapshot(undo, redo);
redo.Invoke();
}
}
/// <summary>
/// Removes item at specified index from the source list with history.
/// </summary>
/// <typeparam name="T">The item type.</typeparam>
/// <param name="source">The source list.</param>
/// <param name="item">The item to remove.</param>
/// <param name="history">The history object.</param>
public static void DeleteWithHistory<T>(this IList<T> source, T item, IHistory history)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (item == null)
throw new ArgumentNullException(nameof(item));
if (history == null)
throw new ArgumentNullException(nameof(history));
if (!history.IsPaused)
{
int index = source.IndexOf(item);
Action redo = () => source.RemoveAt(index);
Action undo = () => source.Insert(index, item);
history.Snapshot(undo, redo);
redo.Invoke();
}
}
/// <summary>
/// Removes item from the source list with history.
/// </summary>
/// <typeparam name="T">The item type.</typeparam>
/// <param name="source">The source list.</param>
/// <param name="index">The item index to remove.</param>
/// <param name="history">The history object.</param>
public static void DeleteWithHistory<T>(this IList<T> source, int index, IHistory history)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (index < 0)
throw new IndexOutOfRangeException("Index can not be negative.");
if (history == null)
throw new ArgumentNullException(nameof(history));
if (!history.IsPaused)
{
T item = source[index];
Action redo = () => source.RemoveAt(index);
Action undo = () => source.Insert(index, item);
history.Snapshot(undo, redo);
redo.Invoke();
}
}
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
namespace ReactiveHistory
{
/// <summary>
/// Undo/redo action pair.
/// </summary>
public struct State
{
/// <summary>
/// The undo state action.
/// </summary>
public readonly Action Undo;
/// <summary>
/// The redo state action.
/// </summary>
public readonly Action Redo;
/// <summary>
/// The undo state name.
/// </summary>
public readonly string UndoName;
/// <summary>
/// The redo state name.
/// </summary>
public readonly string RedoName;
/// <summary>
/// Initializes a new <see cref="State"/> instance.
/// </summary>
/// <param name="undo">The undo state action.</param>
/// <param name="redo">The redo state action.</param>
/// <param name="undoName">The undo state name.</param>
/// <param name="redoName">The redo state name.</param>
public State(Action undo, Action redo, string undoName = null, string redoName = null)
{
if (undo == null)
throw new ArgumentNullException(nameof(undo));
if (redo == null)
throw new ArgumentNullException(nameof(redo));
Undo = undo;
Redo = redo;
UndoName = undoName;
RedoName = redoName;
}
}
}

Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="System.Reactive" version="3.0.0" targetFramework="portable45-net45+win8" />
<package id="System.Reactive.Core" version="3.0.0" targetFramework="portable45-net45+win8" />
<package id="System.Reactive.Interfaces" version="3.0.0" targetFramework="portable45-net45+win8" />
<package id="System.Reactive.Linq" version="3.0.0" targetFramework="portable45-net45+win8" />
<package id="System.Reactive.PlatformServices" version="3.0.0" targetFramework="portable45-net45+win8" />
</packages>

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
namespace ReactiveHistory.UnitTests
{
internal class HistoryHelper : IDisposable
{
CompositeDisposable _disposable;
IList<bool> _canUndos;
IList<bool> _canRedos;
IList<bool> _canClears;
public IList<bool> CanUndos { get { return _canUndos; } }
public IList<bool> CanRedos { get { return _canRedos; } }
public IList<bool> CanClears { get { return _canClears; } }
public HistoryHelper(IHistory target)
{
_disposable = new CompositeDisposable();
_canUndos = new List<bool>();
_canRedos = new List<bool>();
_canClears = new List<bool>();
_disposable.Add(target.CanUndo.Subscribe(x => _canUndos.Add(x)));
_disposable.Add(target.CanRedo.Subscribe(x => _canRedos.Add(x)));
_disposable.Add(target.CanClear.Subscribe(x => _canClears.Add(x)));
}
public void Dispose()
{
_disposable.Dispose();
}
}
}

Просмотреть файл

@ -0,0 +1,96 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Xunit;
namespace ReactiveHistory.UnitTests
{
public class ObservableHistoryExtensionsTests
{
[Fact]
[Trait("ReactiveHistory", "ObservableHistoryExtensions")]
public void ObserveWithHistory_Skips_First_Value()
{
var target = new StackHistory();
var subject = new Subject<int>();
var disposable = subject.AsObservable().ObserveWithHistory(x => { }, 0, target);
subject.OnNext(1);
Assert.Equal(0, target.Undos.Count);
}
[Fact]
[Trait("ReactiveHistory", "ObservableHistoryExtensions")]
public void ObserveWithHistory_Creates_History_Snapshot()
{
var target = new StackHistory();
var subject = new Subject<int>();
var disposable = subject.AsObservable().ObserveWithHistory(x => { }, 0, target);
subject.OnNext(1);
subject.OnNext(2);
subject.OnNext(3);
Assert.Equal(2, target.Undos.Count);
}
[Fact]
[Trait("ReactiveHistory", "ObservableHistoryExtensions")]
public void ObserveWithHistory_Does_Not_Create_History_Snapshot_When_IsPaused_True()
{
var target = new StackHistory();
var subject = new Subject<int>();
var disposable = subject.AsObservable().ObserveWithHistory(x => { }, 0, target);
subject.OnNext(1);
subject.OnNext(2);
target.IsPaused = true;
subject.OnNext(3);
subject.OnNext(4);
target.IsPaused = false;
subject.OnNext(5);
subject.OnNext(6);
Assert.Equal(3, target.Undos.Count);
}
[Fact]
[Trait("ReactiveHistory", "ObservableHistoryExtensions")]
public void ObserveWithHistory_Sets_CurrentValue()
{
var history = new StackHistory();
var subject = new Subject<int>();
var target = new List<int>();
var initialValue = 10;
var disposable = subject.AsObservable().ObserveWithHistory(
x =>
{
target.Add(x);
},
currentValue: initialValue,
history: history);
subject.OnNext(initialValue); // empty -> 10 (the initial state of variable)
subject.OnNext(2); // empty -> 10 -> 2
subject.OnNext(3); // empty -> 10 -> 2 -> 3
history.Undo(); // 3 -> 2
history.Undo(); // 2 -> 10 (finally restores initial state)
Assert.Equal(0, history.Undos.Count);
Assert.Equal(2, history.Redos.Count);
Assert.Equal(new int[] { 2, 10 }, target);
history.Redo(); // 10 -> 2
history.Redo(); // 2 -> 3
Assert.Equal(2, history.Undos.Count);
Assert.Equal(0, history.Redos.Count);
Assert.Equal(new int[] { 2, 10, 2, 3 }, target);
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("ReactiveHistory.UnitTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Wiesław Šoltés")]
[assembly: AssemblyProduct("ReactiveHistory.UnitTests")]
[assembly: AssemblyCopyright("Copyright © Wiesław Šoltés 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("71ba2d95-53e6-42d4-adaa-cf34c77494f5")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Просмотреть файл

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\xunit.runner.visualstudio.2.1.0\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\..\packages\xunit.runner.visualstudio.2.1.0\build\net20\xunit.runner.visualstudio.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{71BA2D95-53E6-42D4-ADAA-CF34C77494F5}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ReactiveHistory.UnitTests</RootNamespace>
<AssemblyName>ReactiveHistory.UnitTests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Reactive.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reactive.Core.3.0.0\lib\net45\System.Reactive.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Interfaces, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reactive.Interfaces.3.0.0\lib\net45\System.Reactive.Interfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Linq, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reactive.Linq.3.0.0\lib\net45\System.Reactive.Linq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.PlatformServices, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reactive.PlatformServices.3.0.0\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Windows.Threading, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reactive.Windows.Threading.3.0.0\lib\net45\System.Reactive.Windows.Threading.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.assert, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.core, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="HistoryHelper.cs" />
<Compile Include="ObservableHistoryExtensionsTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StackHistoryExtensionsTests.cs" />
<Compile Include="StackHistoryTests.cs" />
<Compile Include="StateTests.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ReactiveHistory\ReactiveHistory.csproj">
<Project>{9832484c-4bbe-43e9-b40e-9f57af68a185}</Project>
<Name>ReactiveHistory</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\xunit.runner.visualstudio.2.1.0\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.runner.visualstudio.2.1.0\build\net20\xunit.runner.visualstudio.props'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -0,0 +1,132 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.ObjectModel;
using Xunit;
namespace ReactiveHistory.UnitTests
{
public class StackHistoryExtensionsTests
{
[Fact]
[Trait("ReactiveHistory", "StackHistoryExtensions")]
public void CreateWithHistory_Add_Item_Empty_List()
{
var history = new StackHistory();
var target = new ObservableCollection<Item>();
var item0 = new Item("item0");
target.CreateWithHistory(item0, history);
Assert.Equal(1, target.Count);
Assert.Equal(item0, target[0]);
history.Undo();
Assert.Equal(0, target.Count);
history.Redo();
Assert.Equal(1, target.Count);
Assert.Equal(item0, target[0]);
}
[Fact]
[Trait("ReactiveHistory", "StackHistoryExtensions")]
public void CreateWithHistory_Insert_Item_Empty_List()
{
var history = new StackHistory();
var target = new ObservableCollection<Item>();
var item0 = new Item("item0");
target.CreateWithHistory(0, item0, history);
Assert.Equal(1, target.Count);
Assert.Equal(item0, target[0]);
history.Undo();
Assert.Equal(0, target.Count);
history.Redo();
Assert.Equal(1, target.Count);
Assert.Equal(item0, target[0]);
}
[Fact]
[Trait("ReactiveHistory", "StackHistoryExtensions")]
public void UpdateWithHistory_Replace_Item_Empty_List()
{
var history = new StackHistory();
var target = new ObservableCollection<Item>();
var item0 = new Item("item0");
var item1 = new Item("item0");
target.Add(item0);
Assert.Equal(1, target.Count);
Assert.Equal(item0, target[0]);
target.UpdateWithHistory(0, item1, history);
Assert.Equal(1, target.Count);
Assert.Equal(item1, target[0]);
history.Undo();
Assert.Equal(1, target.Count);
Assert.Equal(item0, target[0]);
history.Redo();
Assert.Equal(1, target.Count);
Assert.Equal(item1, target[0]);
}
[Fact]
[Trait("ReactiveHistory", "StackHistoryExtensions")]
public void DeleteWithHistory_Remove_Item_Empty_List()
{
var history = new StackHistory();
var target = new ObservableCollection<Item>();
var item0 = new Item("item0");
target.Add(item0);
Assert.Equal(1, target.Count);
Assert.Equal(item0, target[0]);
target.DeleteWithHistory(item0, history);
Assert.Equal(0, target.Count);
history.Undo();
Assert.Equal(1, target.Count);
Assert.Equal(item0, target[0]);
history.Redo();
Assert.Equal(0, target.Count);
}
[Fact]
[Trait("ReactiveHistory", "StackHistoryExtensions")]
public void DeleteWithHistory_Remove_Index_Empty_List()
{
var history = new StackHistory();
var target = new ObservableCollection<Item>();
var item0 = new Item("item0");
target.Add(item0);
Assert.Equal(1, target.Count);
Assert.Equal(item0, target[0]);
target.DeleteWithHistory(0, history);
Assert.Equal(0, target.Count);
history.Undo();
Assert.Equal(1, target.Count);
Assert.Equal(item0, target[0]);
history.Redo();
Assert.Equal(0, target.Count);
}
class Item
{
public string Name { get; set; }
public Item(string name)
{
Name = name;
}
}
}
}

Просмотреть файл

@ -0,0 +1,187 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Linq;
using Xunit;
namespace ReactiveHistory.UnitTests
{
public class StackHistoryTests
{
[Fact]
[Trait("ReactiveHistory", "StackHistory")]
public void Undos_And_Redos_Shuould_Be_Initialized()
{
var target = new StackHistory();
using (var helper = new HistoryHelper(target))
{
Assert.NotNull(target.Undos);
Assert.NotNull(target.Redos);
Assert.Equal(0, target.Undos.Count);
Assert.Equal(0, target.Redos.Count);
Assert.Equal(false, target.IsPaused);
Assert.Equal(new bool[] { }, helper.CanUndos.ToArray());
Assert.Equal(new bool[] { }, helper.CanRedos.ToArray());
Assert.Equal(new bool[] { }, helper.CanClears.ToArray());
}
}
[Fact]
[Trait("ReactiveHistory", "StackHistory")]
public void First_Snapshot_Should_Push_One_Undo_State()
{
var target = new StackHistory();
using (var helper = new HistoryHelper(target))
{
target.Snapshot(() => { }, () => { });
Assert.Equal(1, target.Undos.Count);
Assert.Equal(0, target.Redos.Count);
Assert.Equal(new bool[] { true }, helper.CanUndos.ToArray());
Assert.Equal(new bool[] { }, helper.CanRedos.ToArray());
Assert.Equal(new bool[] { true }, helper.CanClears.ToArray());
}
}
[Fact]
[Trait("ReactiveHistory", "StackHistory")]
public void Snapshot_Should_Clear_Redos()
{
var target = new StackHistory();
using (var helper = new HistoryHelper(target))
{
target.Snapshot(() => { }, () => { });
Assert.Equal(1, target.Undos.Count);
Assert.Equal(0, target.Redos.Count);
var result = target.Undo();
Assert.Equal(0, target.Undos.Count);
Assert.Equal(1, target.Redos.Count);
Assert.Equal(true, result);
target.Snapshot(() => { }, () => { });
Assert.Equal(1, target.Undos.Count);
Assert.Equal(0, target.Redos.Count);
Assert.Equal(new bool[] { true, false, true }, helper.CanUndos.ToArray());
Assert.Equal(new bool[] { true, false }, helper.CanRedos.ToArray());
Assert.Equal(new bool[] { true, true, true }, helper.CanClears.ToArray());
}
}
[Fact]
[Trait("ReactiveHistory", "StackHistory")]
public void Invoking_Undo_Should_Not_Throw_When_Undos_Are_Empty()
{
var target = new StackHistory();
using (var helper = new HistoryHelper(target))
{
var result = target.Undo();
Assert.Equal(false, result);
Assert.Equal(new bool[] { }, helper.CanUndos.ToArray());
Assert.Equal(new bool[] { }, helper.CanRedos.ToArray());
Assert.Equal(new bool[] { }, helper.CanClears.ToArray());
}
}
[Fact]
[Trait("ReactiveHistory", "StackHistory")]
public void Invoking_Redo_Should_Not_Throw_When_Redos_Are_Empty()
{
var target = new StackHistory();
using (var helper = new HistoryHelper(target))
{
var result = target.Redo();
Assert.Equal(false, result);
Assert.Equal(new bool[] { }, helper.CanUndos.ToArray());
Assert.Equal(new bool[] { }, helper.CanRedos.ToArray());
Assert.Equal(new bool[] { }, helper.CanClears.ToArray());
}
}
[Fact]
[Trait("ReactiveHistory", "StackHistory")]
public void Invoking_Undo_Should_Invoke_Undo_Action_And_Push_State_To_Redos()
{
int undoCount = 0;
int redoCount = 0;
var target = new StackHistory();
using (var helper = new HistoryHelper(target))
{
target.Snapshot(() => undoCount++, () => redoCount++);
var undo = target.Undos.Peek();
var result = target.Undo();
Assert.Equal(1, undoCount);
Assert.Equal(0, redoCount);
Assert.Equal(0, target.Undos.Count);
Assert.Equal(1, target.Redos.Count);
Assert.Equal(true, result);
Assert.Equal(new bool[] { true, false }, helper.CanUndos.ToArray());
Assert.Equal(new bool[] { true }, helper.CanRedos.ToArray());
Assert.Equal(new bool[] { true, true }, helper.CanClears.ToArray());
var redo = target.Redos.Peek();
Assert.Equal(undo, redo);
}
}
[Fact]
[Trait("ReactiveHistory", "StackHistory")]
public void Invoking_Redo_Should_Invoke_Redo_Action_And_Push_State_To_Undos()
{
int undoCount = 0;
int redoCount = 0;
var target = new StackHistory();
using (var helper = new HistoryHelper(target))
{
target.Snapshot(() => undoCount++, () => redoCount++);
var undo1 = target.Undos.Peek();
var result1 = target.Undo();
var redo = target.Redos.Peek();
var result2 = target.Redo();
Assert.Equal(1, target.Undos.Count);
Assert.Equal(0, target.Redos.Count);
Assert.Equal(true, result1);
Assert.Equal(true, result2);
Assert.Equal(new bool[] { true, false, true }, helper.CanUndos.ToArray());
Assert.Equal(new bool[] { true, false }, helper.CanRedos.ToArray());
Assert.Equal(new bool[] { true, true, true }, helper.CanClears.ToArray());
var undo2 = target.Undos.Peek();
Assert.Equal(undo1, undo2);
Assert.Equal(1, undoCount);
Assert.Equal(1, redoCount);
Assert.Equal(true, result1);
}
}
[Fact]
[Trait("ReactiveHistory", "StackHistory")]
public void Undo_Sets_IsPaused_True_While_Invoking_Undo_Redo_State()
{
var target = new StackHistory();
target.Snapshot(
undo: () =>
{
Assert.True(target.IsPaused);
},
redo: () =>
{
Assert.True(target.IsPaused);
});
Assert.False(target.IsPaused);
target.Undo();
Assert.False(target.IsPaused);
Assert.False(target.IsPaused);
target.Redo();
Assert.False(target.IsPaused);
}
}
}

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using Xunit;
namespace ReactiveHistory.UnitTests
{
public class StateTests
{
[Fact]
[Trait("ReactiveHistory", "State")]
public void Constructor_Should_Throw_When_Undo_Parameter_Is_Nulll()
{
Assert.Throws<ArgumentNullException>(
"undo",
() =>
{
var target = new State(null, () => { });
});
}
[Fact]
[Trait("ReactiveHistory", "State")]
public void Constructor_Should_Throw_When_Redo_Parameter_Is_Nulll()
{
Assert.Throws<ArgumentNullException>(
"redo",
() =>
{
var target = new State(() => { }, null);
});
}
[Fact]
[Trait("ReactiveHistory", "State")]
public void Constructor_Should_Set_Undo_And_Redo_Fields()
{
Action undo = () => { };
Action redo = () => { };
var target = new State(undo, redo);
Assert.Equal(undo, target.Undo);
Assert.Equal(redo, target.Redo);
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="System.Reactive" version="3.0.0" targetFramework="net45" />
<package id="System.Reactive.Core" version="3.0.0" targetFramework="net45" />
<package id="System.Reactive.Interfaces" version="3.0.0" targetFramework="net45" />
<package id="System.Reactive.Linq" version="3.0.0" targetFramework="net45" />
<package id="System.Reactive.PlatformServices" version="3.0.0" targetFramework="net45" />
<package id="System.Reactive.Windows.Threading" version="3.0.0" targetFramework="net45" />
<package id="xunit" version="2.1.0" targetFramework="net45" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net45" />
<package id="xunit.assert" version="2.1.0" targetFramework="net45" />
<package id="xunit.core" version="2.1.0" targetFramework="net45" />
<package id="xunit.extensibility.core" version="2.1.0" targetFramework="net45" />
<package id="xunit.extensibility.execution" version="2.1.0" targetFramework="net45" />
<package id="xunit.runner.visualstudio" version="2.1.0" targetFramework="net45" />
</packages>