Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Build and test

on:
push:
branches:
- main
pull_request:

permissions:
contents: read

jobs:
build-and-test:
runs-on: windows-latest
timeout-minutes: 10

steps:
- name: Check out source
uses: actions/checkout@v6

- name: Set up .NET 8
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.x

- name: Restore dependencies
run: dotnet restore WorkNotes.sln

- name: Build release configuration
run: dotnet build WorkNotes.sln --configuration Release --no-restore

- name: Run tests
run: dotnet test WorkNotes.sln --configuration Release --no-build --no-restore --verbosity normal
89 changes: 89 additions & 0 deletions WorkNotes.Tests/LinkTextParserTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using WorkNotes.Services;

namespace WorkNotes.Tests;

public sealed class LinkTextParserTests
{
[Fact]
public void FindLinks_ReturnsNoLinksForEmptyText()
{
Assert.Empty(LinkTextParser.FindLinks(string.Empty));
}

[Fact]
public void FindLinks_DetectsEmailWithoutOverlappingDomain()
{
const string text = "Contact dev@example.com for help.";

var link = Assert.Single(LinkTextParser.FindLinks(text));

Assert.Equal("dev@example.com", link.DisplayText);
Assert.Equal("mailto:dev@example.com", link.Url);
Assert.Equal(text.IndexOf("dev@example.com", StringComparison.Ordinal), link.StartOffset);
Assert.Equal(link.StartOffset + link.DisplayText.Length, link.EndOffset);
}

[Theory]
[InlineData("https://example.com/docs", "https://example.com/docs")]
[InlineData("http://example.com", "http://example.com")]
[InlineData("www.example.com/path", "https://www.example.com/path")]
[InlineData("example.com/path?q=1#top", "https://example.com/path?q=1#top")]
public void FindLinks_NormalizesSupportedUrlForms(string text, string expectedUrl)
{
var link = Assert.Single(LinkTextParser.FindLinks(text));

Assert.Equal(text, link.DisplayText);
Assert.Equal(expectedUrl, link.Url);
}

[Theory]
[InlineData("Visit example.com.", "example.com")]
[InlineData("Visit example.com, then continue", "example.com")]
[InlineData("Visit (example.com)", "example.com")]
[InlineData("Visit [example.com]", "example.com")]
public void FindLinks_TrimsSentencePunctuation(string text, string expectedDisplayText)
{
var link = Assert.Single(LinkTextParser.FindLinks(text));

Assert.Equal(expectedDisplayText, link.DisplayText);
Assert.Equal("https://" + expectedDisplayText, link.Url);
}

[Fact]
public void FindLinks_PreservesBalancedParenthesesInUrl()
{
const string text = "https://example.com/wiki/Function_(mathematics)";

var link = Assert.Single(LinkTextParser.FindLinks(text));

Assert.Equal(text, link.DisplayText);
Assert.Equal(text.Length, link.EndOffset);
}

[Fact]
public void FindLinks_AppliesBaseOffsetToExclusiveRange()
{
const string text = "example.com";

var link = Assert.Single(LinkTextParser.FindLinks(text, baseOffset: 17));

Assert.Equal(17, link.StartOffset);
Assert.Equal(17 + text.Length, link.EndOffset);
}

[Fact]
public void FindLinks_ReturnsMixedLinksInDocumentOrder()
{
const string text = "See example.com or email team@example.org, then visit https://docs.example.net/start.";

var links = LinkTextParser.FindLinks(text);

Assert.Collection(
links,
link => Assert.Equal("example.com", link.DisplayText),
link => Assert.Equal("team@example.org", link.DisplayText),
link => Assert.Equal("https://docs.example.net/start", link.DisplayText));
Assert.True(links[0].EndOffset <= links[1].StartOffset);
Assert.True(links[1].EndOffset <= links[2].StartOffset);
}
}
29 changes: 29 additions & 0 deletions WorkNotes.Tests/WorkNotes.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\WorkNotes\WorkNotes.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

</Project>
30 changes: 18 additions & 12 deletions WorkNotes.sln
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkNotes", "WorkNotes\WorkNotes.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkNotes.Tests", "WorkNotes.Tests\WorkNotes.Tests.csproj", "{B0DA7728-F09E-4076-96F1-D37A0C653DBA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
{B0DA7728-F09E-4076-96F1-D37A0C653DBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0DA7728-F09E-4076-96F1-D37A0C653DBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0DA7728-F09E-4076-96F1-D37A0C653DBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0DA7728-F09E-4076-96F1-D37A0C653DBA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
16 changes: 10 additions & 6 deletions WorkNotes/WorkNotes.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@
<FileVersion>1.5.0.0</FileVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
<PackageReference Include="WeCantSpell.Hunspell" Version="7.0.1" />
</ItemGroup>

<ItemGroup>
<ItemGroup>
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
<PackageReference Include="WeCantSpell.Hunspell" Version="7.0.1" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="WorkNotes.Tests" />
</ItemGroup>

<ItemGroup>
<!-- Remove from default None glob, re-include as Content so dotnet publish copies them -->
<None Remove="Dictionaries\*.*" />
<Content Include="Dictionaries\*.*">
Expand Down