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
40 changes: 25 additions & 15 deletions .github/actions/run-tests/action.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: "Run tests"
description: "Runs the specified tests using factos or core tests"
description: "Runs the specified tests using ui or core tests"
inputs:
use-factos:
description: 'Whether to use factos for test selection'
test-type:
description: 'The type of tests to run (factos, core or snapshot)'
required: true
id:
description: "Identifier for the test target"
Expand Down Expand Up @@ -54,7 +54,7 @@ runs:
# run tests using the factos selection and the console when no emulator is needed
- name: Run UI tests
uses: nick-fields/retry@v3.0.2
if : ${{ inputs.use-factos == 'true' && inputs.use-android-emulator != 'true' }}
if : ${{ inputs.test-type == 'factos' && inputs.use-android-emulator != 'true' }}
with:
timeout_minutes: 15
max_attempts: 3
Expand All @@ -63,14 +63,14 @@ runs:

# run tests using the factos selection inside the android emulator
- name: Enable KVM
if : ${{ inputs.use-factos == 'true' && inputs.use-android-emulator == 'true' }}
if : ${{ inputs.test-type == 'factos' && inputs.use-android-emulator == 'true' }}
shell: pwsh
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Create Test Script
if : ${{ inputs.use-factos == 'true' && inputs.use-android-emulator == 'true' }}
if : ${{ inputs.test-type == 'factos' && inputs.use-android-emulator == 'true' }}
shell: bash
run: |
cat << 'EOF' > run_tests.sh
Expand All @@ -86,29 +86,39 @@ runs:
EOF
chmod +x run_tests.sh
- name: Start Android emulator and run tests
if : ${{ inputs.use-factos == 'true' && inputs.use-android-emulator == 'true' }}
if : ${{ inputs.test-type == 'factos' && inputs.use-android-emulator == 'true' }}
uses: reactivecircus/android-emulator-runner@v2.35.0
with:
api-level: 35
target: google_apis
arch: x86_64
script: ./run_tests.sh

# run core tests when factos is not used
# the Debug config is required even we are testing the nuget packages in Release.
# this is because some optimizations in Release make the tests fail, but the code
# that fails is not related to the nuget packages but to the test code itself
# there is a function that mocks the ui drawing that fails in Release optimizations.
# ToDo: fix that, or now that we have ui tests, maybe just run the tests in the ui?.
- name: Run tests
- name: Run core tests
uses: nick-fields/retry@v3.0.2
if : ${{ inputs.use-factos != 'true' }}
if : ${{ inputs.test-type == 'core' }}
with:
timeout_minutes: 8
max_attempts: 3
shell: pwsh
command: dotnet run --project tests/CoreTests/CoreTests.csproj -c Debug -f ${{ inputs.target-framework }} --report-trx -p:UseNuGetPackages=true -p:LiveChartsVersionSuffix=-${{ github.sha }}

- name: Run snapshot tests
uses: nick-fields/retry@v3.0.2
if : ${{ inputs.test-type == 'snapshot' }}
with:
Comment on lines 55 to +109

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

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

The step conditions use if : (with a space) instead of the supported if: key. GitHub Actions will treat if as an unknown key, so these conditions may be ignored and steps could run unexpectedly. Rename if : to if: for all affected steps in this file.

Copilot uses AI. Check for mistakes.
timeout_minutes: 8
max_attempts: 3
shell: pwsh
command: dotnet run --project tests/SnapshotTests/SnapshotTests.csproj -c Release --report-trx -p:UseNuGetPackages=true -p:LiveChartsVersionSuffix=-${{ github.sha }}

- name: Upload snapshot diffs
if: failure() && inputs.test-type == 'snapshot'
uses: actions/upload-artifact@v6.0.0
with:
name: snapshot-diffs
path: tests/SnapshotTests/bin/Release/net10.0/SnapshotsDiff/**/*

- name: Upload test results
if: always()
uses: actions/upload-artifact@v6.0.0
Expand Down
36 changes: 27 additions & 9 deletions .github/workflows/livecharts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,27 @@ jobs:
with:
id: core
test-id: core-${{ matrix.framework }}
use-factos: false
test-type: core
target-framework: ${{ matrix.framework }}
comment_id: ${{ needs.report-progress-starting.outputs.comment_id }}

test-snapshot:
runs-on: windows-2025
needs: [pack, report-progress-starting, collect-packages]
strategy:
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v6.0.1

- name: Run tests
uses: ./.github/actions/run-tests
with:
id: snapshot
test-id: snapshot
test-type: snapshot
comment_id: ${{ needs.report-progress-starting.outputs.comment_id }}

test-windows:
runs-on: windows-2025
strategy:
Expand Down Expand Up @@ -163,7 +180,7 @@ jobs:
uses: ./.github/actions/run-tests
with:
id: windows
use-factos: true
test-type: factos
test-id: ${{ matrix.id }}
workloads: ${{ matrix.workloads }}
target-framework: ${{ matrix.tf }}
Expand Down Expand Up @@ -192,7 +209,7 @@ jobs:
uses: ./.github/actions/run-tests
with:
id: linux
use-factos: true
test-type: factos
test-id: ${{ matrix.id }}
workloads: ${{ matrix.workloads }}
target-framework: ${{ matrix.tf }}
Expand Down Expand Up @@ -221,7 +238,7 @@ jobs:
uses: ./.github/actions/run-tests
with:
id: mac
use-factos: true
test-type: factos
test-id: ${{ matrix.id }}
workloads: ${{ matrix.workloads }}
target-framework: ${{ matrix.tf }}
Expand Down Expand Up @@ -252,7 +269,7 @@ jobs:
uses: ./.github/actions/run-tests
with:
id: browser
use-factos: true
test-type: factos
test-id: ${{ matrix.id }}
workloads: ${{ matrix.workloads }}
target-framework: ${{ matrix.tf }}
Expand Down Expand Up @@ -282,7 +299,7 @@ jobs:
uses: ./.github/actions/run-tests
with:
id: android
use-factos: true
test-type: factos
test-id: ${{ matrix.id }}
workloads: ${{ matrix.workloads }}
target-framework: ${{ matrix.tf }}
Expand Down Expand Up @@ -313,7 +330,7 @@ jobs:
uses: ./.github/actions/run-tests
with:
id: ios
use-factos: true
test-type: factos
test-id: ${{ matrix.id }}
workloads: ${{ matrix.workloads }}
target-framework: ${{ matrix.tf }}
Expand All @@ -326,6 +343,7 @@ jobs:
needs:
- report-progress-starting
- test-core
- test-snapshot
- test-windows
- test-linux
- test-mac
Expand Down Expand Up @@ -356,7 +374,7 @@ jobs:
with:
comment_id: ${{ needs.report-progress-starting.outputs.comment_id }}
content: |
core #${{ needs.test-core.result }}, windows #${{ needs.test-windows.result }}, linux #${{ needs.test-linux.result }}, mac #${{ needs.test-mac.result }}, browser #${{ needs.test-browser.result }}, android #${{ needs.test-android.result }}, ios #${{ needs.test-ios.result }}
Core #${{ needs.test-core.result }} | Snapshot #${{ needs.test-snapshot.result }} | Windows #${{ needs.test-windows.result }} | Linux #${{ needs.test-linux.result }} | Mac #${{ needs.test-mac.result }} | Browser #${{ needs.test-browser.result }} | Android #${{ needs.test-android.result }} | iOS #${{ needs.test-ios.result }}

### Test Results Summary (Passed) ✅ (4 / 4)

Expand All @@ -367,7 +385,7 @@ jobs:
comment_id: ${{ needs.report-progress-starting.outputs.comment_id }}
content: |

Core #${{ needs.test-core.result }} | Windows #${{ needs.test-windows.result }} | Linux #${{ needs.test-linux.result }} | Mac #${{ needs.test-mac.result }} | Browser #${{ needs.test-browser.result }} | Android #${{ needs.test-android.result }} | iOS #${{ needs.test-ios.result }}
Core #${{ needs.test-core.result }} | Snapshot #${{ needs.test-snapshot.result }} | Windows #${{ needs.test-windows.result }} | Linux #${{ needs.test-linux.result }} | Mac #${{ needs.test-mac.result }} | Browser #${{ needs.test-browser.result }} | Android #${{ needs.test-android.result }} | iOS #${{ needs.test-ios.result }}

### Test Results Summary (Failure) ❌ (4 / 4)

4 changes: 4 additions & 0 deletions LiveCharts.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@
<BuildType Solution="AppStore|*" Project="Release" />
</Project>
<Project Path="tests/SharedUITests/SharedUITests.shproj" Id="730ba90c-a574-4b17-9eeb-c28a4d782449" />
<Project Path="tests/SnapshotTests/SnapshotTests.csproj" Id="12006a54-fa9e-4c2c-8e52-a53eb02cb45b">
<BuildType Solution="Ad-Hoc|*" Project="Release" />
<BuildType Solution="AppStore|*" Project="Release" />
</Project>
<Project Path="tests/UITests/UITests.csproj" Id="78f0491c-c08c-406c-ae95-7af7ccdcfdca">
<BuildType Solution="Ad-Hoc|*" Project="Release" />
<BuildType Solution="AppStore|*" Project="Release" />
Expand Down
5 changes: 5 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"test": {
"runner": "Microsoft.Testing.Platform"
}
Comment thread
beto-rodriguez marked this conversation as resolved.
}
1 change: 1 addition & 0 deletions src/LiveChartsCore/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#else

[assembly: InternalsVisibleTo("CoreTests")]
[assembly: InternalsVisibleTo("SnapshotTests")]
[assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView")]
[assembly: InternalsVisibleTo("LiveChartsCore.Behaviours")]
[assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.WinForms")]
Expand Down
6 changes: 5 additions & 1 deletion src/LiveChartsCore/CartesianChartEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,11 @@ protected internal override void Measure()
ce._isInternalSet = false;
}

if (axis.IsVisible) AddVisual(axis.ChartElementSource);
if (axis.IsVisible)
{
AddVisual(axis.ChartElementSource);
axis.InvalidateCrosshair(this, _pointerPosition);

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

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

Measure() now calls axis.InvalidateCrosshair(this, _pointerPosition) for every visible axis. InvalidateCrosshair does not check chart._isPointerIn, so crosshairs may get re-created during measure even after the pointer has left (pointer position remains inside draw margin). Consider gating this call on _isPointerIn (and/or clearing crosshairs when _isPointerIn is false) to avoid crosshair visuals sticking around unexpectedly.

Suggested change
axis.InvalidateCrosshair(this, _pointerPosition);
if (_isPointerIn)
{
axis.InvalidateCrosshair(this, _pointerPosition);
}

Copilot uses AI. Check for mistakes.
}
axis.ChartElementSource.RemoveOldPaints(View); // <- this is probably obsolete.
// the probable issue is the "IsVisible" property
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using LiveChartsCore.Drawing;
using LiveChartsCore.SkiaSharpView.Drawing.Geometries;
Expand All @@ -36,6 +33,7 @@ internal static class DrawingTextExtensions
{
internal static readonly PositionedBlob s_newLine = new(SKTextBlob.Create(string.Empty, new()), -1);
private static readonly Dictionary<string, SKShaper> s_knownShapers = [];
private static readonly object s_lock = new();

internal static void DrawLabel(this SKCanvas canvas, LabelGeometry label, float opacity = 1)
{
Expand Down Expand Up @@ -288,10 +286,15 @@ private static PositionedBlob ShapeAndPlace(string text, SKFont font, SKPaint pa
var typeface = font.Typeface ??
throw new Exception("A Typeface is required at this point.");

if (!s_knownShapers.TryGetValue(typeface.FamilyName, out var shaper))
SKShaper? shaper = null;

lock (s_lock)
{
shaper = new SKShaper(typeface);
s_knownShapers[typeface.FamilyName] = shaper;
if (!s_knownShapers.TryGetValue(typeface.FamilyName, out shaper))
{
shaper = new SKShaper(typeface);
s_knownShapers[typeface.FamilyName] = shaper;
}
}

var result = shaper.Shape(text, paint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ static InMemorySkiaSharpChart()
/// <value>
/// The background.
/// </value>
public SKColor Background { get; set; } = SKColors.White;
public SKColor Background { get; set; } = SKColors.Empty;

/// <summary>
/// Gets or sets the height.
Expand Down Expand Up @@ -151,7 +151,9 @@ public virtual void DrawOnCanvas(SKCanvas canvas)
if (coreChart is null || coreChart is not Chart skiaChart)
throw new Exception("Something is missing :(");

var bg = coreChart.GetTheme().VirtualBackroundColor.AsSKColor();
var bg = Background == SKColors.Empty
? coreChart.GetTheme().VirtualBackroundColor.AsSKColor()
: Background;
Comment on lines +154 to +156

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

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

Using SKColors.Empty as a sentinel for “use theme background” prevents consumers from requesting a transparent background, because SKColors.Empty is also the natural way to represent fully transparent. Consider switching Background to SKColor? (null = use theme), or adding an explicit UseThemeBackground flag so transparent backgrounds remain possible.

Suggested change
var bg = Background == SKColors.Empty
? coreChart.GetTheme().VirtualBackroundColor.AsSKColor()
: Background;
var bg = Background
?? coreChart.GetTheme().VirtualBackroundColor.AsSKColor();

Copilot uses AI. Check for mistakes.

if (_drawnView is not null)
{
Expand Down
Loading
Loading