diff --git a/components/Carousel/OpenSolution.bat b/components/Carousel/OpenSolution.bat new file mode 100644 index 00000000..814a56d4 --- /dev/null +++ b/components/Carousel/OpenSolution.bat @@ -0,0 +1,3 @@ +@ECHO OFF + +powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %* \ No newline at end of file diff --git a/components/Carousel/samples/Assets/icon.png b/components/Carousel/samples/Assets/icon.png new file mode 100644 index 00000000..8435bcaa Binary files /dev/null and b/components/Carousel/samples/Assets/icon.png differ diff --git a/components/Carousel/samples/Carousel.Samples.csproj b/components/Carousel/samples/Carousel.Samples.csproj new file mode 100644 index 00000000..7b4d6dd9 --- /dev/null +++ b/components/Carousel/samples/Carousel.Samples.csproj @@ -0,0 +1,10 @@ + + + + + Carousel + + + + + diff --git a/components/Carousel/samples/Carousel.md b/components/Carousel/samples/Carousel.md new file mode 100644 index 00000000..070c8677 --- /dev/null +++ b/components/Carousel/samples/Carousel.md @@ -0,0 +1,65 @@ +--- +title: Carousel +author: githubaccount +description: TODO: Your experiment's description here +keywords: Carousel, Control, Layout +dev_langs: + - csharp +category: Controls +subcategory: Layout +discussion-id: 0 +issue-id: 0 +icon: assets/icon.png +--- + + + + + + + + + +# Carousel + +TODO: Fill in information about this experiment and how to get started here... + +## Custom Control + +You can inherit from an existing component as well, like `Panel`, this example shows a control without a +XAML Style that will be more light-weight to consume by an app developer: + +> [!Sample CarouselCustomSample] + +## Templated Controls + +The Toolkit is built with templated controls. This provides developers a flexible way to restyle components +easily while still inheriting the general functionality a control provides. The examples below show +how a component can use a default style and then get overridden by the end developer. + +TODO: Two types of templated control building methods are shown. Delete these if you're building a custom component. +Otherwise, pick one method for your component and delete the files related to the unchosen `_ClassicBinding` or `_xBind` +classes (and the custom non-suffixed one as well). Then, rename your component to just be your component name. + +The `_ClassicBinding` class shows the traditional method used to develop components with best practices. + +### Implict style + +> [!SAMPLE CarouselTemplatedSample] + +### Custom style + +> [!SAMPLE CarouselTemplatedStyleCustomSample] + +## Templated Controls with x:Bind + +This is an _experimental_ new way to define components which allows for the use of x:Bind within the style. + +### Implict style + +> [!SAMPLE CarouselXbindBackedSample] + +### Custom style + +> [!SAMPLE CarouselXbindBackedStyleCustomSample] + diff --git a/components/Carousel/samples/CarouselCustomSample.xaml b/components/Carousel/samples/CarouselCustomSample.xaml new file mode 100644 index 00000000..392e630e --- /dev/null +++ b/components/Carousel/samples/CarouselCustomSample.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/components/Carousel/samples/CarouselCustomSample.xaml.cs b/components/Carousel/samples/CarouselCustomSample.xaml.cs new file mode 100644 index 00000000..c70ff312 --- /dev/null +++ b/components/Carousel/samples/CarouselCustomSample.xaml.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using CommunityToolkit.WinUI.Controls; + +namespace CarouselExperiment.Samples; + +/// +/// An example sample page of a custom control inheriting from Panel. +/// +[ToolkitSampleTextOption("TitleText", "This is a title", Title = "Input the text")] +[ToolkitSampleMultiChoiceOption("LayoutOrientation", "Horizontal", "Vertical", Title = "Orientation")] + +[ToolkitSample(id: nameof(CarouselCustomSample), "Custom control", description: $"A sample for showing how to create and use a {nameof(Carousel)} custom control.")] +public sealed partial class CarouselCustomSample : Page +{ + public CarouselCustomSample() + { + this.InitializeComponent(); + } + + // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149 + public static Orientation ConvertStringToOrientation(string orientation) => orientation switch + { + "Vertical" => Orientation.Vertical, + "Horizontal" => Orientation.Horizontal, + _ => throw new System.NotImplementedException(), + }; +} diff --git a/components/Carousel/samples/CarouselTemplatedSample.xaml b/components/Carousel/samples/CarouselTemplatedSample.xaml new file mode 100644 index 00000000..70ded607 --- /dev/null +++ b/components/Carousel/samples/CarouselTemplatedSample.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/components/Carousel/samples/CarouselTemplatedSample.xaml.cs b/components/Carousel/samples/CarouselTemplatedSample.xaml.cs new file mode 100644 index 00000000..86f32c20 --- /dev/null +++ b/components/Carousel/samples/CarouselTemplatedSample.xaml.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace CarouselExperiment.Samples; + +[ToolkitSampleBoolOption("IsTextVisible", true, Title = "IsVisible")] +// Single values without a colon are used for both label and value. +// To provide a different label for the value, separate with a colon surrounded by a single space on both sides ("label : value"). +[ToolkitSampleNumericOption("TextSize", 12, 8, 48, 2, false, Title = "FontSize")] +[ToolkitSampleMultiChoiceOption("TextFontFamily", "Segoe UI", "Arial", "Consolas", Title = "Font family")] +[ToolkitSampleMultiChoiceOption("TextForeground", "Teal : #0ddc8c", "Sand : #e7a676", "Dull green : #5d7577", Title = "Text foreground")] + +[ToolkitSample(id: nameof(CarouselTemplatedSample), "Templated control", description: "A sample for showing how to create and use a templated control.")] +public sealed partial class CarouselTemplatedSample : Page +{ + public CarouselTemplatedSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml b/components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml new file mode 100644 index 00000000..08dff9a7 --- /dev/null +++ b/components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml.cs b/components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml.cs new file mode 100644 index 00000000..3fc9960b --- /dev/null +++ b/components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace CarouselExperiment.Samples; + +[ToolkitSampleBoolOption("IsTextVisible", true, Title = "IsVisible")] +// Single values without a colon are used for both label and value. +// To provide a different label for the value, separate with a colon surrounded by a single space on both sides ("label : value"). +[ToolkitSampleNumericOption("TextSize", 12, 8, 48, 2, true, Title = "FontSize")] +[ToolkitSampleMultiChoiceOption("TextFontFamily", "Segoe UI", "Arial", "Consolas", Title = "Font family")] +[ToolkitSampleMultiChoiceOption("TextForeground", "Teal : #0ddc8c", "Sand : #e7a676", "Dull green : #5d7577", Title = "Text foreground")] + +[ToolkitSample(id: nameof(CarouselTemplatedStyleCustomSample), "Templated control (restyled)", description: "A sample for showing how to create a use and templated control with a custom style.")] +public sealed partial class CarouselTemplatedStyleCustomSample : Page +{ + public CarouselTemplatedStyleCustomSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Carousel/samples/CarouselXbindBackedSample.xaml b/components/Carousel/samples/CarouselXbindBackedSample.xaml new file mode 100644 index 00000000..1c195009 --- /dev/null +++ b/components/Carousel/samples/CarouselXbindBackedSample.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/components/Carousel/samples/CarouselXbindBackedSample.xaml.cs b/components/Carousel/samples/CarouselXbindBackedSample.xaml.cs new file mode 100644 index 00000000..a3d9c35a --- /dev/null +++ b/components/Carousel/samples/CarouselXbindBackedSample.xaml.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace CarouselExperiment.Samples; + +[ToolkitSampleBoolOption("IsTextVisible", true, Title = "IsVisible")] +// Single values without a colon are used for both label and value. +// To provide a different label for the value, separate with a colon surrounded by a single space on both sides ("label : value"). +[ToolkitSampleNumericOption("TextSize", 12, 8, 48, 2, false, Title = "FontSize")] +[ToolkitSampleMultiChoiceOption("TextFontFamily", "Segoe UI", "Arial", "Consolas", Title = "Font family")] +[ToolkitSampleMultiChoiceOption("TextForeground", "Teal : #0ddc8c", "Sand : #e7a676", "Dull green : #5d7577", Title = "Text foreground")] + +[ToolkitSample(id: nameof(CarouselXbindBackedSample), "Backed templated control", description: "A sample for showing how to create and use a templated control with a backed resource dictionary.")] +public sealed partial class CarouselXbindBackedSample : Page +{ + public CarouselXbindBackedSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml b/components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml new file mode 100644 index 00000000..d860f3dd --- /dev/null +++ b/components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml.cs b/components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml.cs new file mode 100644 index 00000000..6123f44a --- /dev/null +++ b/components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace CarouselExperiment.Samples; + +[ToolkitSampleBoolOption("IsTextVisible", true, Title = "IsVisible")] +// Single values without a colon are used for both label and value. +// To provide a different label for the value, separate with a colon surrounded by a single space on both sides ("label : value"). +[ToolkitSampleNumericOption("TextSize", 12, 8, 48, 2, true, Title = "FontSize")] +[ToolkitSampleMultiChoiceOption("TextFontFamily", "Segoe UI", "Arial", "Consolas", Title = "Font family")] +[ToolkitSampleMultiChoiceOption("TextForeground", "Teal : #0ddc8c", "Sand : #e7a676", "Dull green : #5d7577", Title = "Text foreground")] + +[ToolkitSample(id: nameof(CarouselXbindBackedStyleCustomSample), "Backed templated control (restyled)", description: "A sample for showing how to create and use a templated control with a backed resource dictionary and a custom style.")] +public sealed partial class CarouselXbindBackedStyleCustomSample : Page +{ + public CarouselXbindBackedStyleCustomSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Carousel/samples/Dependencies.props b/components/Carousel/samples/Dependencies.props new file mode 100644 index 00000000..e622e1df --- /dev/null +++ b/components/Carousel/samples/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/Carousel/src/Carousel.Properties.cs b/components/Carousel/src/Carousel.Properties.cs new file mode 100644 index 00000000..99ace3e9 --- /dev/null +++ b/components/Carousel/src/Carousel.Properties.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; + +namespace CommunityToolkit.WinUI.Controls; + +public partial class Carousel +{ + /// + /// The backing for the property. + /// + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(Carousel), new PropertyMetadata(Orientation.Horizontal)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty SelectedIndexProperty = + DependencyProperty.Register(nameof(SelectedIndex), typeof(int), typeof(Carousel), new PropertyMetadata(-1, OnCarouselSelectionPropertyChanged)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty SelectedItemProperty = + DependencyProperty.Register(nameof(SelectedItem), typeof(object), typeof(Carousel), new PropertyMetadata(null, OnCarouselSelectionPropertyChanged)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ItemSpacingProperty = + DependencyProperty.Register(nameof(ItemSpacing), typeof(double), typeof(Carousel), new PropertyMetadata(0)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty TransitionDurationProperty = + DependencyProperty.Register(nameof(TransitionDuration), typeof(TimeSpan), typeof(Carousel), new PropertyMetadata(TimeSpan.FromMilliseconds(200))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty EasingFunctionProperty = + DependencyProperty.Register(nameof(TransitionDuration), typeof(EasingFunctionBase), typeof(Carousel), new PropertyMetadata(null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ItemDepthProperty = + DependencyProperty.Register(nameof(ItemDepth), typeof(double), typeof(Carousel), new PropertyMetadata(0)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ItemRotationProperty = + DependencyProperty.Register(nameof(ItemRotation), typeof(double), typeof(Carousel), new PropertyMetadata(Vector3.Zero)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty InvertPositiveProperty = + DependencyProperty.Register(nameof(InvertPositive), typeof(bool), typeof(Carousel), new PropertyMetadata(true)); + + /// + /// Gets or sets the orientation to render items. + /// + public Orientation Orientation + { + get => (Orientation)GetValue(OrientationProperty); + set => SetValue(OrientationProperty, value); + } + + /// + /// Gets or sets the index of the selected item. + /// + public int SelectedIndex + { + get => (int)GetValue(SelectedIndexProperty); + set => SetValue(SelectedIndexProperty, value); + } + + /// + /// Gets or sets the selected item. + /// + public object? SelectedItem + { + get => (object?)GetValue(SelectedItemProperty); + set => SetValue(SelectedItemProperty, value); + } + + /// + /// Gets or sets the selected item. + /// + public double? ItemSpacing + { + get => (double?)GetValue(ItemSpacingProperty); + set => SetValue(ItemSpacingProperty, value); + } + + /// + /// Gets or sets the transition animation duration. + /// + public TimeSpan TransitionDuration + { + get => (TimeSpan)GetValue(TransitionDurationProperty); + set => SetValue(TransitionDurationProperty, value); + } + + /// + /// Gets or sets the transition animation duration. + /// + public EasingFunctionBase? EasingFunction + { + get => (EasingFunctionBase?)GetValue(EasingFunctionProperty); + set => SetValue(EasingFunctionProperty, value); + } + + /// + /// Gets or sets the depth for unselected items. + /// + public double ItemDepth + { + get => (double)GetValue(TransitionDurationProperty); + set => SetValue(TransitionDurationProperty, value); + } + + /// + /// Gets or sets the rotation to apply to carousel items. + /// + public Vector3 ItemRotation + { + get => (Vector3)GetValue(ItemRotationProperty); + set => SetValue(ItemRotationProperty, value); + } + + /// + /// Gets or sets the rotation to apply to carousel items. + /// + public bool InvertPositive + { + get => (bool)GetValue(InvertPositiveProperty); + set => SetValue(InvertPositiveProperty, value); + } + + private static void OnCarouselSelectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not Carousel carousel) + return; + } +} diff --git a/components/Carousel/src/Carousel.cs b/components/Carousel/src/Carousel.cs new file mode 100644 index 00000000..7160f8d3 --- /dev/null +++ b/components/Carousel/src/Carousel.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace CommunityToolkit.WinUI.Controls; + +/// +/// An example templated control. +/// +public partial class Carousel : ItemsControl +{ + /// + /// Occurs when the selected item changes. + /// + public event SelectionChangedEventHandler? SelectionChanged; + + /// + /// Creates a new instance of the class. + /// + public Carousel() + { + this.DefaultStyleKey = typeof(Carousel); + } + + /// + /// Returns the container used for each item + /// + /// Returns always a CarouselItem + protected override DependencyObject GetContainerForItemOverride() + => new CarouselItem(); +} diff --git a/components/Carousel/src/CarouselItem.cs b/components/Carousel/src/CarouselItem.cs new file mode 100644 index 00000000..b2689f45 --- /dev/null +++ b/components/Carousel/src/CarouselItem.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace CommunityToolkit.WinUI.Controls; + +/// +/// Represents the container for an item in a control +/// +public partial class CarouselItem : SelectorItem +{ + private const string PointerOverState = "PointerOver"; + private const string PointerOverSelectedState = "PointerOverSelected"; + private const string PressedState = "Pressed"; + private const string PressedSelectedState = "PressedSelected"; + private const string SelectedState = "Selected"; + private const string NormalState = "Normal"; + + /// + /// Fired when the is selected. + /// + public event EventHandler? Selected; + + /// + /// Initializes a new instance of the class. + /// + public CarouselItem() + { + DefaultStyleKey = typeof(CarouselItem); + + RegisterPropertyChangedCallback(IsSelectedProperty, OnIsSelectedChanged); + } + + /// + protected override void OnPointerEntered(PointerRoutedEventArgs e) + { + base.OnPointerEntered(e); + + VisualStateManager.GoToState(this, IsSelected ? PointerOverSelectedState : PointerOverState, true); + } + + /// + protected override void OnPointerExited(PointerRoutedEventArgs e) + { + base.OnPointerExited(e); + + VisualStateManager.GoToState(this, IsSelected ? SelectedState : NormalState, true); + } + + /// + protected override void OnPointerPressed(PointerRoutedEventArgs e) + { + base.OnPointerPressed(e); + + VisualStateManager.GoToState(this, IsSelected ? PressedSelectedState : PressedState, true); + } + + private void OnIsSelectedChanged(DependencyObject sender, DependencyProperty dp) + { + var item = (CarouselItem)sender; + + if (item.IsSelected) + { + Selected?.Invoke(this, EventArgs.Empty); + VisualStateManager.GoToState(item, SelectedState, true); + } + else + { + VisualStateManager.GoToState(item, NormalState, true); + } + } +} diff --git a/components/Carousel/src/CarouselStyle.xaml b/components/Carousel/src/CarouselStyle.xaml new file mode 100644 index 00000000..61d2b81d --- /dev/null +++ b/components/Carousel/src/CarouselStyle.xaml @@ -0,0 +1,66 @@ + + + + + + + + diff --git a/components/Carousel/src/CommunityToolkit.WinUI.Controls.Carousel.csproj b/components/Carousel/src/CommunityToolkit.WinUI.Controls.Carousel.csproj new file mode 100644 index 00000000..84430ea1 --- /dev/null +++ b/components/Carousel/src/CommunityToolkit.WinUI.Controls.Carousel.csproj @@ -0,0 +1,14 @@ + + + + + Carousel + This package contains Carousel. + + + CommunityToolkit.WinUI.Controls.CarouselRns + + + + + diff --git a/components/Carousel/src/Dependencies.props b/components/Carousel/src/Dependencies.props new file mode 100644 index 00000000..e622e1df --- /dev/null +++ b/components/Carousel/src/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/Carousel/src/MultiTarget.props b/components/Carousel/src/MultiTarget.props new file mode 100644 index 00000000..b11c1942 --- /dev/null +++ b/components/Carousel/src/MultiTarget.props @@ -0,0 +1,9 @@ + + + + uwp;wasdk;wpf;wasm;linuxgtk;macos;ios;android; + + \ No newline at end of file diff --git a/components/Carousel/src/Themes/Generic.xaml b/components/Carousel/src/Themes/Generic.xaml new file mode 100644 index 00000000..301137bb --- /dev/null +++ b/components/Carousel/src/Themes/Generic.xaml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/components/Carousel/tests/Carousel.Tests.projitems b/components/Carousel/tests/Carousel.Tests.projitems new file mode 100644 index 00000000..378417eb --- /dev/null +++ b/components/Carousel/tests/Carousel.Tests.projitems @@ -0,0 +1,23 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 4FFF589B-BBD2-47F1-A5D6-0568F4801D79 + + + CarouselTests + + + + + ExampleCarouselTestPage.xaml + + + + + Designer + MSBuild:Compile + + + \ No newline at end of file diff --git a/components/Carousel/tests/Carousel.Tests.shproj b/components/Carousel/tests/Carousel.Tests.shproj new file mode 100644 index 00000000..8ad0ea28 --- /dev/null +++ b/components/Carousel/tests/Carousel.Tests.shproj @@ -0,0 +1,13 @@ + + + + 4FFF589B-BBD2-47F1-A5D6-0568F4801D79 + 14.0 + + + + + + + + diff --git a/components/Carousel/tests/ExampleCarouselTestClass.cs b/components/Carousel/tests/ExampleCarouselTestClass.cs new file mode 100644 index 00000000..cc205524 --- /dev/null +++ b/components/Carousel/tests/ExampleCarouselTestClass.cs @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using CommunityToolkit.Tooling.TestGen; +using CommunityToolkit.Tests; +using CommunityToolkit.WinUI.Controls; + +namespace CarouselTests; + +[TestClass] +public partial class ExampleCarouselTestClass : VisualUITestBase +{ + // If you don't need access to UI objects directly or async code, use this pattern. + [TestMethod] + public void SimpleSynchronousExampleTest() + { + var assembly = typeof(Carousel).Assembly; + var type = assembly.GetType(typeof(Carousel).FullName ?? string.Empty); + + Assert.IsNotNull(type, "Could not find Carousel type."); + Assert.AreEqual(typeof(Carousel), type, "Type of Carousel does not match expected type."); + } + + // If you don't need access to UI objects directly, use this pattern. + [TestMethod] + public async Task SimpleAsyncExampleTest() + { + await Task.Delay(250); + + Assert.IsTrue(true); + } + + // Example that shows how to check for exception throwing. + [TestMethod] + public void SimpleExceptionCheckTest() + { + // If you need to check exceptions occur for invalid inputs, etc... + // Use Assert.ThrowsException to limit the scope to where you expect the error to occur. + // Otherwise, using the ExpectedException attribute could swallow or + // catch other issues in setup code. + Assert.ThrowsException(() => throw new NotImplementedException()); + } + + // The UIThreadTestMethod automatically dispatches to the UI for us to work with UI objects. + [UIThreadTestMethod] + public void SimpleUIAttributeExampleTest() + { + var component = new Carousel(); + Assert.IsNotNull(component); + } + + // The UIThreadTestMethod can also easily grab a XAML Page for us by passing its type as a parameter. + // This lets us actually test a control as it would behave within an actual application. + // The page will already be loaded by the time your test is called. + [UIThreadTestMethod] + public void SimpleUIExamplePageTest(ExampleCarouselTestPage page) + { + // You can use the Toolkit Visual Tree helpers here to find the component by type or name: + var component = page.FindDescendant(); + + Assert.IsNotNull(component); + + var componentByName = page.FindDescendant("CarouselControl"); + + Assert.IsNotNull(componentByName); + } + + // You can still do async work with a UIThreadTestMethod as well. + [UIThreadTestMethod] + public async Task SimpleAsyncUIExamplePageTest(ExampleCarouselTestPage page) + { + // This helper can be used to wait for a rendering pass to complete. + // Note, this is already done by loading a Page with the [UIThreadTestMethod] helper. + await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { }); + + var component = page.FindDescendant(); + + Assert.IsNotNull(component); + } + + //// ----------------------------- ADVANCED TEST SCENARIOS ----------------------------- + + // If you need to use DataRow, you can use this pattern with the UI dispatch still. + // Otherwise, checkout the UIThreadTestMethod attribute above. + // See https://github.com/CommunityToolkit/Labs-Windows/issues/186 + [TestMethod] + public async Task ComplexAsyncUIExampleTest() + { + await EnqueueAsync(() => + { + var component = new Carousel(); + Assert.IsNotNull(component); + }); + } + + // If you want to load other content not within a XAML page using the UIThreadTestMethod above. + // Then you can do that using the Load/UnloadTestContentAsync methods. + [TestMethod] + public async Task ComplexAsyncLoadUIExampleTest() + { + await EnqueueAsync(async () => + { + var component = new Carousel(); + Assert.IsNotNull(component); + Assert.IsFalse(component.IsLoaded); + + await LoadTestContentAsync(component); + + Assert.IsTrue(component.IsLoaded); + + await UnloadTestContentAsync(component); + + Assert.IsFalse(component.IsLoaded); + }); + } + + // You can still use the UIThreadTestMethod to remove the extra layer for the dispatcher as well: + [UIThreadTestMethod] + public async Task ComplexAsyncLoadUIExampleWithoutDispatcherTest() + { + var component = new Carousel(); + Assert.IsNotNull(component); + Assert.IsFalse(component.IsLoaded); + + await LoadTestContentAsync(component); + + Assert.IsTrue(component.IsLoaded); + + await UnloadTestContentAsync(component); + + Assert.IsFalse(component.IsLoaded); + } +} diff --git a/components/Carousel/tests/ExampleCarouselTestPage.xaml b/components/Carousel/tests/ExampleCarouselTestPage.xaml new file mode 100644 index 00000000..86719c0f --- /dev/null +++ b/components/Carousel/tests/ExampleCarouselTestPage.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/components/Carousel/tests/ExampleCarouselTestPage.xaml.cs b/components/Carousel/tests/ExampleCarouselTestPage.xaml.cs new file mode 100644 index 00000000..766077ea --- /dev/null +++ b/components/Carousel/tests/ExampleCarouselTestPage.xaml.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace CarouselTests; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class ExampleCarouselTestPage : Page +{ + public ExampleCarouselTestPage() + { + this.InitializeComponent(); + } +}