@@ -28,7 +28,8 @@ public static class ElmTestRunner
2828 public static ElmTestRun CompileAndRunTests (
2929 string appDirectory ,
3030 IPineVM ? pineVm = null ,
31- string ? filter = null )
31+ string ? filter = null ,
32+ bool listTests = false )
3233 {
3334 appDirectory = Path . GetFullPath ( appDirectory ) ;
3435
@@ -76,6 +77,7 @@ file.path[0] is "tests" &&
7677
7778 return
7879 ( file . path ,
80+ filePathText : string . Join ( '/' , file . path ) ,
7981 moduleName : moduleHeader . ModuleName ,
8082 moduleNameText : string . Join ( '.' , moduleHeader . ModuleName ) ) ;
8183 } )
@@ -121,15 +123,37 @@ file.path[0] is "tests" &&
121123 "Did not find declaration '" + testModule . moduleNameText + ".suite'" ) ;
122124 }
123125
124- DiscoverTests ( suiteValue , path : [ ] , discoveredTests ) ;
126+ DiscoverTests (
127+ suiteValue ,
128+ filePath : testModule . filePathText ,
129+ descriptionPath : [ ] ,
130+ discoveredTests ) ;
125131 }
126132
127133 if ( filter is { } filterText )
128134 {
129135 discoveredTests . RemoveAll (
130136 test =>
137+ ! test . FilePath . Contains ( filterText , StringComparison . OrdinalIgnoreCase ) &&
131138 ! test . Path . Any (
132- name => name . Contains ( filterText , StringComparison . OrdinalIgnoreCase ) ) ) ;
139+ pathItem =>
140+ pathItem . Contains ( filterText , StringComparison . OrdinalIgnoreCase ) ) ) ;
141+ }
142+
143+ if ( listTests )
144+ {
145+ return
146+ new ElmTestRun . Listed (
147+ [
148+ .. discoveredTests
149+ . Where ( test => test . Kind is not DiscoveredTestKind . EmptyGroup )
150+ . Select (
151+ test =>
152+ new ListedTest (
153+ test . FilePath ,
154+ test . Path . SkipLast ( 1 ) . ToImmutableArray ( ) ,
155+ test . Path [ ^ 1 ] ) )
156+ ] ) ;
133157 }
134158
135159 var parseCache = new PineVMParseCache ( ) ;
@@ -425,7 +449,8 @@ private static IntermediatePineVM CreatePineVm() =>
425449
426450 private static void DiscoverTests (
427451 PineValue testValue ,
428- IReadOnlyList < string > path ,
452+ string filePath ,
453+ IReadOnlyList < string > descriptionPath ,
429454 List < DiscoveredTest > discoveredTests )
430455 {
431456 var ( tag , arguments ) = ParseTaggedValue ( testValue ) ;
@@ -436,7 +461,7 @@ private static void DiscoverTests(
436461 throw new InvalidOperationException ( "Describe must contain two arguments" ) ;
437462
438463 var groupName = ParseElmString ( arguments . Span [ 0 ] ) ;
439- var groupPath = path . Append ( groupName ) . ToImmutableArray ( ) ;
464+ var groupPath = descriptionPath . Append ( groupName ) . ToImmutableArray ( ) ;
440465
441466 if ( arguments . Span [ 1 ] is not PineValue . ListValue children )
442467 throw new InvalidOperationException ( "Describe children must be a list" ) ;
@@ -445,6 +470,7 @@ private static void DiscoverTests(
445470 {
446471 discoveredTests . Add (
447472 new DiscoveredTest (
473+ filePath ,
448474 groupPath ,
449475 DiscoveredTestKind . EmptyGroup ,
450476 Thunk : null ) ) ;
@@ -453,7 +479,7 @@ private static void DiscoverTests(
453479 }
454480
455481 foreach ( var child in children . Items . Span )
456- DiscoverTests ( child , groupPath , discoveredTests ) ;
482+ DiscoverTests ( child , filePath , groupPath , discoveredTests ) ;
457483
458484 return ;
459485 }
@@ -465,7 +491,8 @@ private static void DiscoverTests(
465491
466492 discoveredTests . Add (
467493 new DiscoveredTest (
468- [ .. path , ParseElmString ( arguments . Span [ 0 ] ) ] ,
494+ filePath ,
495+ [ .. descriptionPath , ParseElmString ( arguments . Span [ 0 ] ) ] ,
469496 DiscoveredTestKind . Runnable ,
470497 arguments . Span [ 1 ] ) ) ;
471498
@@ -479,7 +506,8 @@ private static void DiscoverTests(
479506
480507 discoveredTests . Add (
481508 new DiscoveredTest (
482- [ .. path , ParseElmString ( arguments . Span [ 0 ] ) ] ,
509+ filePath ,
510+ [ .. descriptionPath , ParseElmString ( arguments . Span [ 0 ] ) ] ,
483511 DiscoveredTestKind . Todo ,
484512 Thunk : null ) ) ;
485513
@@ -522,6 +550,7 @@ private enum DiscoveredTestKind
522550
523551
524552 private sealed record DiscoveredTest (
553+ string FilePath ,
525554 IReadOnlyList < string > Path ,
526555 DiscoveredTestKind Kind ,
527556 PineValue ? Thunk ) ;
@@ -1020,6 +1049,64 @@ public void Deconstruct(
10201049}
10211050
10221051
1052+ /// <summary>
1053+ /// Describes a discovered Elm test.
1054+ /// </summary>
1055+ public sealed record ListedTest
1056+ {
1057+ /// <summary>
1058+ /// Creates a description of a discovered Elm test.
1059+ /// </summary>
1060+ public ListedTest (
1061+ string filePath ,
1062+ IReadOnlyList < string > descriptionPath ,
1063+ string name )
1064+ {
1065+ FilePath = filePath ;
1066+ DescriptionPath = descriptionPath ;
1067+ Name = name ;
1068+ }
1069+
1070+ /// <summary>
1071+ /// Gets the test module's path relative to the Elm project.
1072+ /// </summary>
1073+ public string FilePath { get ; init ; }
1074+
1075+ /// <summary>
1076+ /// Gets the path of nested descriptions containing the test.
1077+ /// </summary>
1078+ public IReadOnlyList < string > DescriptionPath { get ; init ; }
1079+
1080+ /// <summary>
1081+ /// Gets the test name.
1082+ /// </summary>
1083+ public string Name { get ; init ; }
1084+
1085+ /// <inheritdoc/>
1086+ public bool Equals ( ListedTest ? other ) =>
1087+ ReferenceEquals ( this , other ) ||
1088+ ( other is not null &&
1089+ FilePath == other . FilePath &&
1090+ DescriptionPath . SequenceEqual ( other . DescriptionPath , StringComparer . Ordinal ) &&
1091+ Name == other . Name ) ;
1092+
1093+ /// <inheritdoc/>
1094+ public override int GetHashCode ( )
1095+ {
1096+ var hashCode = new HashCode ( ) ;
1097+
1098+ hashCode . Add ( FilePath , StringComparer . Ordinal ) ;
1099+
1100+ foreach ( var description in DescriptionPath )
1101+ hashCode . Add ( description , StringComparer . Ordinal ) ;
1102+
1103+ hashCode . Add ( Name , StringComparer . Ordinal ) ;
1104+
1105+ return hashCode . ToHashCode ( ) ;
1106+ }
1107+ }
1108+
1109+
10231110/// <summary>
10241111/// Contains a styled fragment of rendered test output.
10251112/// </summary>
@@ -1102,6 +1189,41 @@ public sealed record Completed(
11021189 TimeSpan Duration )
11031190 : ElmTestRun ;
11041191
1192+ /// <summary>
1193+ /// Contains tests discovered without running them.
1194+ /// </summary>
1195+ public sealed record Listed : ElmTestRun
1196+ {
1197+ /// <summary>
1198+ /// Creates a result containing tests discovered without running them.
1199+ /// </summary>
1200+ public Listed ( IReadOnlyList < ListedTest > tests )
1201+ {
1202+ Tests = tests ;
1203+ }
1204+
1205+ /// <summary>
1206+ /// Gets the discovered tests.
1207+ /// </summary>
1208+ public IReadOnlyList < ListedTest > Tests { get ; init ; }
1209+
1210+ /// <inheritdoc/>
1211+ public bool Equals ( Listed ? other ) =>
1212+ ReferenceEquals ( this , other ) ||
1213+ ( other is not null && Tests . SequenceEqual ( other . Tests ) ) ;
1214+
1215+ /// <inheritdoc/>
1216+ public override int GetHashCode ( )
1217+ {
1218+ var hashCode = new HashCode ( ) ;
1219+
1220+ foreach ( var test in Tests )
1221+ hashCode . Add ( test ) ;
1222+
1223+ return hashCode . ToHashCode ( ) ;
1224+ }
1225+ }
1226+
11051227 /// <summary>
11061228 /// Represents a test run for a project without Elm test modules.
11071229 /// </summary>
0 commit comments