Expiriments with AvaloniaEdit and tracking document changes

This commit is contained in:
eelke 2025-08-30 19:41:10 +02:00
parent 29a141a971
commit 6325409d25
53 changed files with 643 additions and 627 deletions

View file

@ -14,7 +14,8 @@ public class PqConnectionStringParserTests
tokenizer
.AddString(kw)
.AddEquals()
.AddString(val);
.AddString(val)
.AddEof();
}
[Fact]

View file

@ -1,4 +1,5 @@
using pgLabII.PgUtils.ConnectionStrings;
using pgLabII.PgUtils.Tests.ConnectionStrings.Util;
namespace pgLabII.PgUtils.Tests.ConnectionStrings;
@ -11,18 +12,18 @@ public class PqConnectionStringTokenizerTests
public void GetKeyword_Success(string input, string expected)
{
PqConnectionStringTokenizer subject = new(input);
Assert.Equal(expected, subject.GetKeyword());
var result = subject.GetKeyword();
ResultAssert.Success(result, expected);
}
[Theory]
[InlineData("=")]
[InlineData("")]
[InlineData(" ")]
public void GetKeyword_Throws(string input)
public void GetKeyword_Errors(string input)
{
PqConnectionStringTokenizer subject = new(input);
Assert.Throws<PqConnectionStringParserException>(() => subject.GetKeyword());
ResultAssert.Failed(subject.GetKeyword());
}
[Theory]
@ -36,7 +37,7 @@ public class PqConnectionStringTokenizerTests
{
PqConnectionStringTokenizer subject = new(input);
Assert.Equal(expected, subject.Eof);
Assert.Equal(expected, subject.IsEof);
}
[Theory]
@ -46,7 +47,7 @@ public class PqConnectionStringTokenizerTests
public void ConsumeEquals_Success(string input)
{
PqConnectionStringTokenizer subject = new(input);
subject.ConsumeEquals();
ResultAssert.Success(subject.ConsumeEquals());
}
[Theory]
@ -57,7 +58,8 @@ public class PqConnectionStringTokenizerTests
public void ConsumeEquals_Throws(string input)
{
PqConnectionStringTokenizer subject = new(input);
Assert.Throws<PqConnectionStringParserException>(() => subject.ConsumeEquals());
var result = subject.ConsumeEquals();
ResultAssert.Failed(result);
}
[Theory]
@ -69,7 +71,8 @@ public class PqConnectionStringTokenizerTests
public void GetValue_Success(string input, string expected)
{
PqConnectionStringTokenizer subject = new(input);
Assert.Equal(expected, subject.GetValue());
var result = subject.GetValue();
ResultAssert.Success(result, expected);
}
[Theory]
@ -80,6 +83,7 @@ public class PqConnectionStringTokenizerTests
public void GetValue_Throws(string input)
{
PqConnectionStringTokenizer subject = new(input);
Assert.Throws<PqConnectionStringParserException>(() => subject.GetValue());
var result = subject.GetValue();
ResultAssert.Failed(result);
}
}

View file

@ -0,0 +1,38 @@
using FluentResults;
namespace pgLabII.PgUtils.Tests.ConnectionStrings.Util;
public static class ResultAssert
{
public static void Success(Result result)
{
Assert.True(result.IsSuccess);
}
public static void Success<T>(Result<T> result)
{
Assert.True(result.IsSuccess);
}
public static void Success<T>(Result<T> result, T expected)
{
Assert.True(result.IsSuccess);
Assert.Equal(expected, result.Value);
}
public static void Success<T>(Result<T> result, Action<T> assert)
{
Assert.True(result.IsSuccess);
assert(result.Value);
}
public static void Failed(Result result)
{
Assert.True(result.IsFailed);
}
public static void Failed<T>(Result<T> result)
{
Assert.True(result.IsFailed);
}
}

View file

@ -1,84 +1,93 @@
using pgLabII.PgUtils.ConnectionStrings;
using FluentResults;
using pgLabII.PgUtils.ConnectionStrings;
using OneOf;
namespace pgLabII.PgUtils.Tests.ConnectionStrings;
using Elem = OneOf<PqToken, string, IError>;
internal class UnitTestTokenizer : IPqConnectionStringTokenizer
{
private readonly struct Elem(PqToken result, object? output)
{
public readonly PqToken Result = result;
public readonly object? Output = output;
}
private readonly List<Elem> _tokens = [];
private readonly List<Elem> _tokens = new();
private int _position = 0;
public UnitTestTokenizer AddString(string? output)
public UnitTestTokenizer AddString(string output)
{
_tokens.Add(new(PqToken.String, output));
_tokens.Add(output);
return this;
}
public UnitTestTokenizer AddException(Exception? output)
public UnitTestTokenizer AddError(Error error)
{
_tokens.Add(new(PqToken.Exception, output));
_tokens.Add(error);
return this;
}
public UnitTestTokenizer AddEquals()
{
_tokens.Add(new(PqToken.Equals, null));
_tokens.Add(PqToken.Equals);
return this;
}
public void AddEof()
{
_tokens.Add(PqToken.Eof);
}
// note we do no whitespace at end tests here
public bool Eof => _position >= _tokens.Count;
public string GetKeyword()
public bool IsEof
{
EnsureNotEof();
var elem = Consume();
if (elem.Result == PqToken.String)
return (string)elem.Output!;
throw new Exception("Unexpected call to GetKeyword");
get
{
EnsureNotEol();
return _tokens[_position].IsT0 && _tokens[_position].AsT0 == PqToken.Eof;
}
}
public void ConsumeEquals()
public Result<string> GetKeyword()
{
EnsureNotEof();
EnsureNotEol();
var elem = Consume();
if (elem.Result == PqToken.Equals)
return;
throw new Exception("Unexpected call to ConsumeEquals");
return elem.Match<Result<string>>(
token => throw new Exception("Unexpected call to GetKeyword"),
str => str,
error => Result.Fail(error));
}
public string GetValue()
public Result ConsumeEquals()
{
EnsureNotEof();
EnsureNotEol();
var elem = Consume();
if (elem.Result == PqToken.String)
return (string)elem.Output!;
throw new Exception("Unexpected call to GetValue");
return elem.Match<Result>(
token =>
{
if (token != PqToken.Equals)
throw new Exception("Unexpected call to GetKeyword");
return Result.Ok();
},
str => throw new Exception("Unexpected call to ConsumeEquals"),
error => Result.Fail(error));
}
public Result<string> GetValue()
{
EnsureNotEol();
var elem = Consume();
return elem.Match<Result<string>>(
token => throw new Exception("Unexpected call to GetValue"),
str => str,
error => Result.Fail(error));
}
private Elem Consume()
{
var elem = _tokens[_position++];
if (elem.Result == PqToken.Exception)
{
throw (Exception)elem.Output!;
}
return elem;
return _tokens[_position++];
}
private void EnsureNotEof()
private void EnsureNotEol()
{
if (Eof)
throw new Exception("unexpected eof in test, wrong parser call?");
if (_position >= _tokens.Count)
throw new Exception("unexpected end of list in test, wrong parser call?");
}
}

View file

@ -1,27 +1,40 @@
namespace pgLabII.PgUtils.Tests.ConnectionStrings.Util;
using FluentResults;
namespace pgLabII.PgUtils.Tests.ConnectionStrings.Util;
public class UnitTestTokenizerTests
{
private readonly UnitTestTokenizer _sut = new();
[Fact]
public void Eof_True()
public void IsEof_Throws()
{
Assert.True(_sut.Eof);
Assert.Throws<Exception>(() =>
{
bool _ = _sut.IsEof;
});
}
[Fact]
public void IsEof_True()
{
_sut.AddEof();
Assert.True(_sut.IsEof);
}
[Fact]
public void Eof_False()
{
_sut.AddString("a");
Assert.False(_sut.Eof);
Assert.False(_sut.IsEof);
}
[Fact]
public void GetKeyword_Success()
{
_sut.AddString("a");
Assert.Equal("a", _sut.GetKeyword());
var result = _sut.GetKeyword();
ResultAssert.Success(result, "a");
}
[Fact]
@ -34,15 +47,17 @@ public class UnitTestTokenizerTests
[Fact]
public void GetKeyword_SimulatesException()
{
_sut.AddException(new ArgumentNullException());
Assert.Throws<ArgumentNullException>(() => _sut.GetKeyword());
_sut.AddError(new("test"));
var result = _sut.GetKeyword();
ResultAssert.Failed(result);
}
[Fact]
public void GetValue_Success()
{
_sut.AddString("a");
Assert.Equal("a", _sut.GetValue());
var result = _sut.GetValue();
ResultAssert.Success(result, "a");
}
[Fact]
@ -55,15 +70,17 @@ public class UnitTestTokenizerTests
[Fact]
public void GetValue_SimulatesException()
{
_sut.AddException(new ArgumentNullException());
Assert.Throws<ArgumentNullException>(() => _sut.GetValue());
_sut.AddError(new("test"));
var result = _sut.GetValue();
ResultAssert.Failed(result);
}
[Fact]
public void ConsumeEquals_Success()
{
_sut.AddEquals();
_sut.ConsumeEquals();
var result = _sut.ConsumeEquals();
ResultAssert.Success(result);
}
[Fact]

View file

@ -11,6 +11,7 @@
<ItemGroup>
<PackageReference Include="coverlet.collector" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="OneOf" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>

View file

@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=acls/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=connectionstrings_005Cutil/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>