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
34 changes: 34 additions & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net

name: .NET

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This workflow does not set explicit permissions for GITHUB_TOKEN. To reduce risk when running on pull_request events (including from forks), consider adding least-privilege permissions (e.g., contents: read) at the workflow or job level.

Suggested change
build:
build:
permissions:
contents: read

Copilot uses AI. Check for mistakes.

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
Comment on lines +18 to +20

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For supply-chain security, consider pinning GitHub Actions to a full commit SHA (instead of the mutable @v4 tag) for both actions/checkout and actions/setup-dotnet.

Suggested change
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup .NET
uses: actions/setup-dotnet@3f14f4970e29fbfe43b8a0123c2f7bd0d1c7db2f # v4.0.0

Copilot uses AI. Check for mistakes.
with:
dotnet-version: 9.0.x

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider enabling NuGet caching via actions/setup-dotnet (e.g., its built-in cache option) to speed up CI runs and reduce restore time on repeated builds.

Suggested change
dotnet-version: 9.0.x
dotnet-version: 9.0.x
cache: true

Copilot uses AI. Check for mistakes.
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore -c Release -p:TreatWarningsAsErrors=true
- name: Test
run: dotnet test --no-build -c Release --verbosity normal --logger "trx;LogFileName=test_results.trx"
- name: Upload Test Results
uses: actions/upload-artifact@v4
if: always() # This ensures it runs even if tests fail
with:
name: test-results
path: "**/test_results.trx"
45 changes: 36 additions & 9 deletions SlugGeneratorLibrary/SlugGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
using System;
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;

namespace SlugGeneratorLibrary
{
public static class SlugGenerator
{
public static string Generate(string text)
public static string CustomGenerate(string text, char separator)
{
if (text is null)
throw new ArgumentNullException(nameof(text));
text = text.Trim()
.ToLowerInvariant();
text = Regex.Replace(text, @"[+()^*%#@!/\\.,|`~]+", string.Empty);
text = Regex.Replace(text, @"[\s_-]+", "-");
ArgumentNullException.ThrowIfNull(text);
text = Regex.Replace(text.Trim()
.ToLowerInvariant(),
@"[+()^*%#@!/\\.,|`~]+", string.Empty);
text = Regex.Replace(text.Trim(), @"[\s_-]+", separator.ToString());
return text;
}

public static string GenerateHyphens(string text)
{
return CustomGenerate(text, '-');
}

public static string GenerateUnderscores(string text)
{
return CustomGenerate(text, '_');
}

public static string GenerateUnique(string text)
{
ArgumentNullException.ThrowIfNull(text);
// append text with a GUID-based suffix to greatly reduce collision risk
string uniqueText = text;
string slugifiedText = GenerateHyphens(uniqueText);
slugifiedText += '-' + Guid.NewGuid().ToString("N");
return slugifiedText;
}
}

public static class StringExtensions
{
/// <summary>Convert string to slug separated by hyphens.</summary>
public static string ToSlug(this string text)
{
return SlugGenerator.GenerateHyphens(text);
}
}
}
75 changes: 51 additions & 24 deletions SlugGeneratorUnitTesting/SlugAlgorithmUnitTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using SlugGeneratorLibrary;
using Xunit;

namespace SlugGeneratorUnitTesting
{
Expand All @@ -8,64 +7,92 @@ public class SlugAlgorithmUnitTest
[Fact]
public void RemoveWhiteSpaces()
{
Assert.Equal("hello", SlugGenerator.Generate("Hello"));
Assert.Equal("hello", SlugGenerator.Generate(" Hello "));
Assert.Equal("hello", SlugGenerator.GenerateHyphens("Hello"));
Assert.Equal("hello", SlugGenerator.GenerateHyphens(" Hello "));
}

[Fact]
public void ConvertToLowerCase()
{
Assert.Equal("hello", SlugGenerator.Generate("hElLo"));
Assert.Equal("hello", SlugGenerator.GenerateHyphens("hElLo"));
}

[Fact]
public void NullInput()
{
Assert.Throws<ArgumentNullException>(() => SlugGenerator.Generate(null));
Assert.Throws<ArgumentNullException>(() => SlugGenerator.GenerateHyphens(null!));
}

[Fact]
public void EmptyInput()
{
Assert.Equal(string.Empty, SlugGenerator.Generate(string.Empty));
Assert.Equal(string.Empty, SlugGenerator.GenerateHyphens(string.Empty));
}

[Fact]
public void InputWithOnlyWhiteSpaces()
{
Assert.Equal(string.Empty, SlugGenerator.Generate(" "));
Assert.Equal(string.Empty, SlugGenerator.GenerateHyphens(" "));
}

[Fact]
public void InputWithSpecialCharacters()
{
Assert.Equal("hello-world", SlugGenerator.Generate("Hello World"));
Assert.Equal("hello-world", SlugGenerator.Generate("Hello-World"));
Assert.Equal("hello-world", SlugGenerator.Generate("Hello_World"));
Assert.Equal("hello-world", SlugGenerator.Generate("Hello____World"));
Assert.Equal("hello-world", SlugGenerator.Generate("Hello _ World"));
Assert.Equal("hello-world", SlugGenerator.Generate("Hello - _ - World"));
Assert.Equal("hello-world", SlugGenerator.Generate("Hello -_World"));
Assert.Equal("hello-world", SlugGenerator.GenerateHyphens("Hello World"));
Assert.Equal("hello-world", SlugGenerator.GenerateHyphens("Hello-World"));
Assert.Equal("hello-world", SlugGenerator.GenerateHyphens("Hello_World"));
Assert.Equal("hello-world", SlugGenerator.GenerateHyphens("Hello____World"));
Assert.Equal("hello-world", SlugGenerator.GenerateHyphens("Hello _ World"));
Assert.Equal("hello-world", SlugGenerator.GenerateHyphens("Hello - _ - World"));
Assert.Equal("hello-world", SlugGenerator.GenerateHyphens("Hello -_World"));
}

[Fact]
public void InputWithSpecialCharactersToRemove()
{
Assert.Equal("hello", SlugGenerator.Generate("Hello!"));
Assert.Equal("hello", SlugGenerator.Generate("Hello@"));
Assert.Equal("hello", SlugGenerator.Generate("Hello#"));
Assert.Equal("hello", SlugGenerator.Generate("Hello%"));
Assert.Equal("hello", SlugGenerator.Generate("Hello^"));
Assert.Equal("hello", SlugGenerator.Generate("Hello*"));
Assert.Equal("hello", SlugGenerator.Generate("Hello()"));
Assert.Equal("helloworld", SlugGenerator.Generate("Hello()!@#%^*+/\\.|`~,world"));
Assert.Equal("hello-world", SlugGenerator.Generate("Hello_- ()!@#%^*+/\\.|`~,- world"));
Assert.Equal("hello", SlugGenerator.GenerateHyphens("Hello!"));
Assert.Equal("hello", SlugGenerator.GenerateHyphens("Hello@"));
Assert.Equal("hello", SlugGenerator.GenerateHyphens("Hello#"));
Assert.Equal("hello", SlugGenerator.GenerateHyphens("Hello%"));
Assert.Equal("hello", SlugGenerator.GenerateHyphens("Hello^"));
Assert.Equal("hello", SlugGenerator.GenerateHyphens("Hello*"));
Assert.Equal("hello", SlugGenerator.GenerateHyphens("Hello()"));
Assert.Equal("helloworld", SlugGenerator.GenerateHyphens("Hello()!@#%^*+/\\.|`~,world"));
Assert.Equal("hello-world", SlugGenerator.GenerateHyphens("Hello_- ()!@#%^*+/\\.|`~,- world"));
Assert.Equal("hello-world", SlugGenerator.GenerateHyphens("!@# Hello World"));
}

[Fact]
public void InputWithArabicCharacters()
{
Assert.Equal("مرحبا-بالعالم", SlugGenerator.Generate("مرحبا بالعالم"));
Assert.Equal("مرحبا-بالعالم", SlugGenerator.GenerateHyphens("مرحبا بالعالم"));
}

[Fact]
public void SlugStringExtension()
{
Assert.Equal("hello-world", "hello world".ToSlug());
}

[Fact]
public void SlugWithUnderscores()
{
Assert.Equal("hello_world", SlugGenerator.GenerateUnderscores("hello world"));
}

[Fact]
public void CustomGenerate()
{
Assert.Equal("hello.world", SlugGenerator.CustomGenerate("hello world", '.'));
Assert.Equal("hello*world", SlugGenerator.CustomGenerate("hello world", '*'));
}

[Fact]
public void UniqueSlug()
{
string input = "شقة في الجميلية";
Assert.NotEqual(SlugGenerator.GenerateUnique(input),
SlugGenerator.GenerateUnique(input));
}
}
}
Loading