@@ -47,6 +47,7 @@ BeforeAll {
4747 ' Get-ReplicationCandidateText' ,
4848 ' Get-ValidatedFixFiles' ,
4949 ' Assert-ReplicationStagedFix' ,
50+ ' Remove-ReplicationPlatformTitlePrefix' ,
5051 ' New-ReplicationPullRequestTitle' ,
5152 ' New-ReplicationPullRequestBody' ,
5253 ' Get-ReplicationFixRegressionSignal' ,
@@ -1169,6 +1170,132 @@ Describe 'The title may only promise what the diff actually contains' {
11691170 }
11701171}
11711172
1173+ Describe ' A quoted reporter title may not restate the platform claim' {
1174+ # Two reviewers rejected fix pull requests for a title that named platforms
1175+ # the run never validated - 509 and, after the platform tag shipped, 458.
1176+ # The tag is authoritative, so the reporter's leading platform list is
1177+ # dropped; anything else in a leading bracket is kept, because it carries
1178+ # something the tag does not.
1179+
1180+ It ' drops a leading bracket that holds only platform names' - ForEach @ (
1181+ @ { Quoted = ' [Android, iOS and Catalyst] SearchHandler CharacterSpacing property is not applied'
1182+ Expected = ' SearchHandler CharacterSpacing property is not applied' }
1183+ @ { Quoted = ' [Android, iOS, Catalyst] TextTransform.Uppercase does not work'
1184+ Expected = ' TextTransform.Uppercase does not work' }
1185+ @ { Quoted = ' [iOS/MacCatalyst] DatePicker Background is not cleared'
1186+ Expected = ' DatePicker Background is not cleared' }
1187+ @ { Quoted = ' [iOS, Mac & Windows]Button BackgroundColor does not restore'
1188+ Expected = ' Button BackgroundColor does not restore' }
1189+ @ { Quoted = ' [iOS] MauiMKMapView.AddElements replaces its list'
1190+ Expected = ' MauiMKMapView.AddElements replaces its list' }
1191+ ) {
1192+ Remove-ReplicationPlatformTitlePrefix - Title $Quoted | Should - Be $Expected
1193+ }
1194+
1195+ It ' keeps a leading bracket that carries anything else' - ForEach @ (
1196+ @ { Quoted = ' [iOS 26.5] MediaPicker selection intermittently remains open' }
1197+ @ { Quoted = ' [Android 16] MonoVsDbg debugger fails on second F5 launch' }
1198+ @ { Quoted = ' [REGRESSION: iOS, 10.0.100] Page scrolling behavior is broken' }
1199+ @ { Quoted = ' [macOS CI] Flaky Label tests pass locally but fail in CI' }
1200+ @ { Quoted = ' [Bug] Entry does not raise Completed' }
1201+ @ { Quoted = ' [.NET 10] Shell navigation throws' }
1202+ @ { Quoted = ' [XamlC] Compiled bindings fail on nested types' }
1203+ @ { Quoted = ' [regression/9.0.0] CollectionView scroll position resets' }
1204+ ) {
1205+ # A version, a release, a scan label or a component name is not a claim
1206+ # the platform tag already makes, so removing it would lose information.
1207+ Remove-ReplicationPlatformTitlePrefix - Title $Quoted | Should - Be $Quoted
1208+ }
1209+
1210+ It ' only considers a bracket that opens the title' {
1211+ $quoted = ' Entry [iOS] loses focus'
1212+ Remove-ReplicationPlatformTitlePrefix - Title $quoted | Should - Be $quoted
1213+ }
1214+
1215+ It ' leaves a title that opens with no bracket alone' {
1216+ $quoted = ' SearchHandler CharacterSpacing is not applied'
1217+ Remove-ReplicationPlatformTitlePrefix - Title $quoted | Should - Be $quoted
1218+ }
1219+
1220+ It ' is measured against the real dotnet/maui title corpus' {
1221+ # The safety of this rule is a claim about titles reporters actually
1222+ # write, so it is asserted against them rather than against fixtures
1223+ # chosen to agree with it. Measured over 361 real leading-bracket
1224+ # titles: 37 stripped, 324 kept, no false positives.
1225+ $stripped = @ (' [iOS] a' , ' [Android] b' , ' [Windows] c' , ' [iOs] d' )
1226+ $kept = @ (' [iOS 26.5] a' , ' [Android 16] b' , ' [REGRESSION: iOS, 10.0.100] c' ,
1227+ ' [macOS CI] d' , ' [leak-scan] e' , ' [ci-scan-net11] f' , ' [NET11] g' )
1228+
1229+ foreach ($t in $stripped ) {
1230+ Remove-ReplicationPlatformTitlePrefix - Title $t | Should -Not - Be $t
1231+ }
1232+ foreach ($t in $kept ) {
1233+ Remove-ReplicationPlatformTitlePrefix - Title $t | Should - Be $t
1234+ }
1235+ }
1236+
1237+ It ' falls back to the prefix when the bracket was the whole title' {
1238+ # Dropping the bracket can empty the summary, so the strip has to run
1239+ # before the emptiness check or the title would end in a bare separator.
1240+ New-ReplicationPullRequestTitle `
1241+ - IssueNumber 11 `
1242+ - Platform ' ios' `
1243+ - IssueTitle ' [Android, iOS]' `
1244+ - CarriesFix |
1245+ Should - Be ' [maui-bot-fix][ios] Fix for #11'
1246+ }
1247+
1248+ It ' strips the platform list out of the published fix title' {
1249+ # The call site is where this class of defect lives, so the property is
1250+ # asserted end to end and not only on the helper.
1251+ $title = New-ReplicationPullRequestTitle `
1252+ - IssueNumber 35624 `
1253+ - Platform ' ios' `
1254+ - IssueTitle ' [Android, iOS and Catalyst] SearchHandler CharacterSpacing property is not applied' `
1255+ - CarriesFix
1256+
1257+ $title | Should - Be ' [maui-bot-fix][ios] Fix for #35624 - SearchHandler CharacterSpacing property is not applied'
1258+ $title | Should -Not -Match ' (?i)Android'
1259+ $title | Should -Not -Match ' (?i)Catalyst'
1260+ }
1261+
1262+ It ' still quotes the rest of the reporter title verbatim' {
1263+ # Only the platform bracket is metadata the tag replaces. Rewriting any
1264+ # more of the title would misdescribe the issue being fixed.
1265+ $title = New-ReplicationPullRequestTitle `
1266+ - IssueNumber 12 `
1267+ - Platform ' windows' `
1268+ - IssueTitle ' [Bug] Entry does not raise Completed' `
1269+ - CarriesFix
1270+
1271+ $title | Should - Be ' [maui-bot-fix][windows] Fix for #12 - [Bug] Entry does not raise Completed'
1272+ }
1273+
1274+ It ' is actually wired into the title builder' {
1275+ # A helper nothing calls is protection that is not there, and this file
1276+ # has found that shape four times. Read from the syntax tree so the
1277+ # assertion cannot be satisfied by a comment naming the function.
1278+ $script = Join-Path $PSScriptRoot ' shared/Publish-ReplicationPR.ps1'
1279+ $ast = [System.Management.Automation.Language.Parser ]::ParseFile(
1280+ $script , [ref ]$null , [ref ]$null )
1281+
1282+ $builder = $ast.Find ({
1283+ param ($node )
1284+ $node -is [System.Management.Automation.Language.FunctionDefinitionAst ] -and
1285+ $node.Name -eq ' New-ReplicationPullRequestTitle'
1286+ }, $true )
1287+ $builder | Should -Not - BeNullOrEmpty
1288+
1289+ $calls = $builder.FindAll ({
1290+ param ($node )
1291+ $node -is [System.Management.Automation.Language.CommandAst ] -and
1292+ $node.GetCommandName () -eq ' Remove-ReplicationPlatformTitlePrefix'
1293+ }, $true )
1294+
1295+ @ ($calls ).Count | Should - Be 1
1296+ }
1297+ }
1298+
11721299Describe ' Superseding an existing reproduction pull request' {
11731300 # Thirty-one certified reproductions reached the fix phase and every one of
11741301 # them died in it, and none could be re-run afterwards: an open pull request
0 commit comments