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
4 changes: 4 additions & 0 deletions InfoBox/Context/InformationBoxScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public class InformationBoxScope : IDisposable
{
#region Attributes

// TODO: [P1.1] Replace static Stack with AsyncLocal<Stack<InformationBoxScope>> for thread-safety
// See TESTABILITY_ROADMAP.md - current implementation is not thread-safe for concurrent tests
// private static readonly AsyncLocal<Stack<InformationBoxScope>> _scopeStack = new AsyncLocal<Stack<InformationBoxScope>>();
// Also add IInformationBoxScope interface and TestScopeProvider for dependency injection in tests
/// <summary>
/// Stack of all scopes
/// </summary>
Expand Down
14 changes: 14 additions & 0 deletions InfoBox/Form/InformationBoxForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,13 @@ internal InformationBoxForm(string text,
InformationBoxSound sound = InformationBoxSound.Default)
{
this.InitializeComponent();
// TODO: [P0.2] Replace CreateGraphics with ITextMeasurement interface injection
// See TESTABILITY_ROADMAP.md - this prevents headless testing
this.measureGraphics = CreateGraphics();
this.measureGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

// TODO: [P1.3] Replace SystemFonts with ISystemResources interface injection
// See TESTABILITY_ROADMAP.md - this prevents testing without system fonts
// Apply default font for message boxes
this.Font = SystemFonts.MessageBoxFont;
this.messageText.Font = SystemFonts.MessageBoxFont;
Expand Down Expand Up @@ -642,6 +646,8 @@ internal InformationBoxResult Show(out CheckState state)
/// </summary>
private void PlaySound()
{
// TODO: [P1.3] Replace SystemSounds with ISystemResources.PlaySound(InformationBoxSound)
// See TESTABILITY_ROADMAP.md - this entire method logic should move to WindowsSystemResources
if (sound == InformationBoxSound.None)
{
return;
Expand Down Expand Up @@ -1001,6 +1007,8 @@ private void SetFocus()
/// </summary>
private void SetLayout()
{
// TODO: [P0.1] Extract this 110-line method to InformationBoxPresenter.CalculateLayout()
// See TESTABILITY_ROADMAP.md - this complex layout logic should be testable without WinForms
int totalHeight;
int totalWidth;

Expand Down Expand Up @@ -1060,6 +1068,8 @@ private void SetLayout()

totalHeight = Math.Max(iconHeight, textHeight) + BorderPadding * 2 + this.pnlBas.Height;

// TODO: [P1.3] Replace Screen.PrimaryScreen with ISystemResources.GetWorkingArea()
// See TESTABILITY_ROADMAP.md - hardcoded screen metrics prevent testing with different screen sizes
// Add a small space to avoid vertical scrollbar.
if (iconAndTextWidth > Screen.PrimaryScreen.WorkingArea.Width - 100)
{
Expand Down Expand Up @@ -1323,6 +1333,8 @@ private void SetText()
/// </summary>
private void SetButtons()
{
// TODO: [P0.1] Extract button generation logic to InformationBoxPresenter.GetButtons()
// See TESTABILITY_ROADMAP.md - this logic should return List<ButtonDefinition> without WinForms dependencies
// Abort button
if (this.buttons == InformationBoxButtons.AbortRetryIgnore)
{
Expand Down Expand Up @@ -1678,6 +1690,8 @@ private void InformationBoxForm_KeyDown(object sender, KeyEventArgs e)
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void TmrAutoClose_Tick(object sender, EventArgs e)
{
// TODO: [P0.1] Extract this 115-line timer logic to InformationBoxPresenter.UpdateAutoClose(TimeSpan elapsed)
// See TESTABILITY_ROADMAP.md - this should be a pure function returning AutoCloseState without Timer dependencies
if (this.elapsedTime == this.autoClose.Seconds)
{
this.tmrAutoClose.Stop();
Expand Down
6 changes: 6 additions & 0 deletions InfoBox/InformationBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ namespace InfoBox
#endif
public static class InformationBox
{
// TODO: [P1.2] Add factory pattern for testability
// See TESTABILITY_ROADMAP.md - Add IInformationBoxFactory and IInformationBoxDisplay interfaces
// internal static IInformationBoxFactory Factory { get; set; } = new InformationBoxFactory();
// This will allow tests to inject mock factories and test code that calls InformationBox.Show()

#region Show

/// <summary>
Expand Down Expand Up @@ -133,6 +138,7 @@ public static class InformationBox
/// <returns>One of the <see cref="InformationBoxResult"/> values.</returns>
public static InformationBoxResult Show(string text, params object[] parameters)
{
// TODO: [P1.2] Replace direct instantiation with Factory.Create(text, parameters).ShowModal()
return new InformationBoxForm(text, parameters).Show();
}

Expand Down
300 changes: 300 additions & 0 deletions NET48_COMPATIBILITY_REVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
# .NET 4.8 Compatibility Review

## Overview

This document reviews all proposed code changes in TESTABILITY_ROADMAP.md for compatibility with .NET Framework 4.8.

## Summary

**Status**: ⚠️ **One Compatibility Issue Found**

The roadmap contains **one incompatibility** that needs fixing:
- Switch expressions (C# 8.0) in P1.3 `ISystemResources` example

All other proposed features are compatible with .NET 4.8.

---

## Detailed Compatibility Analysis

### ✅ Compatible Features

#### 1. AsyncLocal<T> (P1.1)
- **Feature**: `AsyncLocal<Stack<InformationBoxScope>>`
- **Minimum Version**: .NET Framework 4.6
- **Status**: ✅ **Compatible** with .NET 4.8
- **Notes**: AsyncLocal provides thread-local storage that flows with async context

#### 2. Task/TaskCompletionSource (P2.1)
- **Feature**: `Task<InformationBoxResult>`, `TaskCompletionSource<T>`
- **Minimum Version**: .NET Framework 4.5
- **Status**: ✅ **Compatible** with .NET 4.8
- **Notes**: Task-based async pattern fully supported

#### 3. Async/Await (P2.1)
- **Feature**: `async`/`await` keywords
- **Minimum Version**: .NET Framework 4.5 (C# 5.0)
- **Status**: ✅ **Compatible** with .NET 4.8
- **Notes**: Can be used in consumer code

#### 4. Lambda Expressions
- **Feature**: `(s, e) => tcs.SetResult(display.Result)`
- **Minimum Version**: .NET Framework 3.5 (C# 3.0)
- **Status**: ✅ **Compatible** with .NET 4.8

#### 5. Expression-Bodied Members
- **Feature**: `public Font GetMessageBoxFont() => SystemFonts.MessageBoxFont;`
- **Minimum Version**: C# 6.0 (.NET Framework 4.6+)
- **Status**: ✅ **Compatible** with .NET 4.8
- **Notes**: Requires Visual Studio 2015+ and C# 6.0 compiler

#### 6. Null-Conditional Operator
- **Feature**: `systemSound?.Play();`
- **Minimum Version**: C# 6.0 (.NET Framework 4.6+)
- **Status**: ✅ **Compatible** with .NET 4.8

#### 7. Auto-Property Initializers
- **Feature**: `public Font MessageBoxFont { get; set; } = new Font("Arial", 10);`
- **Minimum Version**: C# 6.0
- **Status**: ✅ **Compatible** with .NET 4.8

#### 8. Generic Collections
- **Feature**: `List<T>`, `Dictionary<TKey, TValue>`
- **Minimum Version**: .NET Framework 2.0
- **Status**: ✅ **Compatible** with .NET 4.8

#### 9. Func<T> and Action<T>
- **Feature**: `Func<IInformationBoxScope>`, `Action`
- **Minimum Version**: .NET Framework 3.5
- **Status**: ✅ **Compatible** with .NET 4.8

---

### ⚠️ Incompatible Features

#### 1. Switch Expressions (P1.3 - ISystemResources)
- **Feature**: `sound switch { ... }`
- **Minimum Version**: C# 8.0 (.NET Core 3.0+)
- **Status**: ⚠️ **INCOMPATIBLE** with .NET 4.8
- **Location**: TESTABILITY_ROADMAP.md, lines 355-363

**Current Code (Incompatible)**:
```csharp
var systemSound = sound switch
{
InformationBoxSound.Beep => SystemSounds.Beep,
InformationBoxSound.Asterisk => SystemSounds.Asterisk,
InformationBoxSound.Exclamation => SystemSounds.Exclamation,
InformationBoxSound.Hand => SystemSounds.Hand,
InformationBoxSound.Question => SystemSounds.Question,
_ => null
};
systemSound?.Play();
```

**Fixed Code (Compatible with .NET 4.8)**:
```csharp
SystemSound systemSound;
switch (sound)
{
case InformationBoxSound.Beep:
systemSound = SystemSounds.Beep;
break;
case InformationBoxSound.Asterisk:
systemSound = SystemSounds.Asterisk;
break;
case InformationBoxSound.Exclamation:
systemSound = SystemSounds.Exclamation;
break;
case InformationBoxSound.Hand:
systemSound = SystemSounds.Hand;
break;
case InformationBoxSound.Question:
systemSound = SystemSounds.Question;
break;
default:
systemSound = null;
break;
}

if (systemSound != null)
{
systemSound.Play();
}
```

**Alternative (Using Dictionary - More Modern)**:
```csharp
private static readonly Dictionary<InformationBoxSound, SystemSound> SoundMap =
new Dictionary<InformationBoxSound, SystemSound>
{
{ InformationBoxSound.Beep, SystemSounds.Beep },
{ InformationBoxSound.Asterisk, SystemSounds.Asterisk },
{ InformationBoxSound.Exclamation, SystemSounds.Exclamation },
{ InformationBoxSound.Hand, SystemSounds.Hand },
{ InformationBoxSound.Question, SystemSounds.Question }
};

public void PlaySound(InformationBoxSound sound)
{
SystemSound systemSound;
if (SoundMap.TryGetValue(sound, out systemSound))
{
systemSound.Play();
}
}
```

---

## C# Language Features by Version

### C# 6.0 (Visual Studio 2015, .NET 4.6+)
✅ Compatible with .NET 4.8:
- Expression-bodied members (`=>`)
- Null-conditional operators (`?.`, `?[]`)
- String interpolation (`$"..."`)
- Auto-property initializers
- `nameof` operator
- Index initializers

### C# 7.0-7.3 (Visual Studio 2017, .NET 4.6.1+)
✅ Compatible with .NET 4.8:
- Tuples with `ValueTuple`
- Pattern matching (basic `is` patterns)
- Out variables (`out var`)
- Local functions
- More expression-bodied members
- Ref locals and returns

### C# 8.0 (Visual Studio 2019, .NET Core 3.0+)
⚠️ **NOT fully compatible** with .NET 4.8:
- Switch expressions ⚠️ **Issue found in roadmap**
- Property patterns
- Tuple patterns
- Using declarations
- Nullable reference types (compiler feature only, works with warnings)
- Async streams
- Default interface methods ⚠️ **Requires runtime support**

### C# 9.0+ (.NET 5.0+)
⚠️ **NOT compatible** with .NET 4.8:
- Records
- Init-only setters
- Top-level statements

---

## Project Configuration Recommendations

### Recommended .csproj Settings for .NET 4.8

```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Target both frameworks -->
<TargetFrameworks>net48;net8.0-windows</TargetFrameworks>

<!-- Use C# 7.3 for maximum .NET 4.8 compatibility -->
<LangVersion>7.3</LangVersion>

<!-- Or allow latest features (but be careful with C# 8.0+) -->
<!-- <LangVersion>latest</LangVersion> -->

<!-- Enable nullable reference types (C# 8.0 compiler feature, works on .NET 4.8) -->
<!-- <Nullable>enable</Nullable> -->
</PropertyGroup>
</Project>
```

### Conditional Compilation for Framework-Specific Code

If needed, use conditional compilation:

```csharp
#if NET5_0_OR_GREATER
// .NET 5/6/7/8+ specific code
var result = items switch
{
null => "null",
[] => "empty",
_ => "has items"
};
#else
// .NET Framework 4.8 compatible code
string result;
if (items == null)
result = "null";
else if (items.Length == 0)
result = "empty";
else
result = "has items";
#endif
```

---

## Testing Library Compatibility

### Unit Testing Frameworks

| Framework | .NET 4.8 Support | Notes |
|-----------|------------------|-------|
| **NUnit** | ✅ Yes (v3.x, v4.x) | Already used in project |
| **xUnit** | ✅ Yes (v2.x) | Alternative option |
| **MSTest** | ✅ Yes (v2.x) | Visual Studio integrated |

### Mocking Libraries

| Library | .NET 4.8 Support | Notes |
|---------|------------------|-------|
| **Moq** | ✅ Yes (v4.x) | Most popular |
| **NSubstitute** | ✅ Yes | Simpler syntax |
| **FakeItEasy** | ✅ Yes | Another alternative |

### UI Automation

| Library | .NET 4.8 Support | Notes |
|---------|------------------|-------|
| **FlaUI** | ✅ Yes | Modern, actively maintained |
| **TestStack.White** | ✅ Yes | Older, less maintained |

### Assertion Libraries

| Library | .NET 4.8 Support | Notes |
|---------|------------------|-------|
| **FluentAssertions** | ✅ Yes (v6.x) | Recommended |
| **Shouldly** | ✅ Yes | Alternative |

---

## Action Items

### Required Fix

1. **Update TESTABILITY_ROADMAP.md** - Fix P1.3 switch expression to use traditional switch statement

### Recommended Actions

1. **Set LangVersion**: Explicitly set `<LangVersion>7.3</LangVersion>` in .csproj for .NET 4.8 builds
2. **Review Code**: Before implementing, verify no C# 8.0+ features creep into .NET 4.8 code paths
3. **CI/CD Testing**: Ensure CI builds and tests both .NET 4.8 and .NET 8+ versions
4. **Documentation**: Add compatibility notes to code comments when using C# 7.0+ features

### Optional Enhancements

1. **Use `#if` conditionals** for framework-specific optimizations
2. **Consider polyfills** for missing APIs (e.g., `System.HashCode` for .NET 4.8)
3. **Test both frameworks** in CI/CD pipeline

---

## Conclusion

The proposed testability improvements are **99% compatible** with .NET 4.8. Only one fix is required:

✅ **Action Required**: Update switch expression in P1.3 to traditional switch statement

All other proposed features (AsyncLocal, Task/async-await, lambda expressions, expression-bodied members, null-conditional operators) are fully supported in .NET 4.8.

The project can safely implement all phases of the testability roadmap with this single correction.
Loading
Loading