@@ -3006,6 +3006,115 @@ flows:
30063006 } ) ;
30073007 } ) ;
30083008
3009+ describe ( 'filterFlowsByTags' , ( ) => {
3010+ const projectDir = path . resolve ( path . sep , 'project' ) ;
3011+ const smokeFlow = path . join ( projectDir , 'smoke.yaml' ) ;
3012+ const flakyFlow = path . join ( projectDir , 'flaky.yaml' ) ;
3013+ const untaggedFlow = path . join ( projectDir , 'untagged.yaml' ) ;
3014+ const configFile = path . join ( projectDir , 'config.yaml' ) ;
3015+ const helperScript = path . join ( projectDir , 'helper.js' ) ;
3016+ const orphanScript = path . join ( projectDir , 'orphan.js' ) ;
3017+ const flowWithDep = path . join ( projectDir , 'with-dep.yaml' ) ;
3018+ const flowWithOrphanDep = path . join ( projectDir , 'with-orphan.yaml' ) ;
3019+
3020+ const fileContents : Record < string , string > = {
3021+ [ smokeFlow ] : `appId: com.example\ntags:\n - smoke\n---\n- launchApp` ,
3022+ [ flakyFlow ] : `appId: com.example\ntags:\n - flaky\n---\n- launchApp` ,
3023+ [ untaggedFlow ] : `appId: com.example\n---\n- launchApp` ,
3024+ [ configFile ] : `flows:\n - "*.yaml"` ,
3025+ [ flowWithDep ] : `appId: com.example\ntags:\n - smoke\n---\n- runScript: helper.js` ,
3026+ [ flowWithOrphanDep ] : `appId: com.example\ntags:\n - flaky\n---\n- runScript: orphan.js` ,
3027+ } ;
3028+
3029+ const buildMaestro = (
3030+ includeTags ?: string [ ] ,
3031+ excludeTags ?: string [ ] ,
3032+ ) : Maestro => {
3033+ const opts = new MaestroOptions (
3034+ 'app.apk' ,
3035+ path . join ( projectDir , 'smoke.yaml' ) ,
3036+ 'Pixel 6' ,
3037+ { includeTags, excludeTags, quiet : true } ,
3038+ ) ;
3039+ return new Maestro ( mockCredentials , opts ) ;
3040+ } ;
3041+
3042+ beforeEach ( ( ) => {
3043+ fs . promises . readFile = jest
3044+ . fn ( )
3045+ . mockImplementation ( ( p : string ) =>
3046+ fileContents [ p ] !== undefined
3047+ ? Promise . resolve ( fileContents [ p ] )
3048+ : Promise . reject ( new Error ( `ENOENT: ${ p } ` ) ) ,
3049+ ) ;
3050+ fs . promises . access = jest . fn ( ) . mockResolvedValue ( undefined ) ;
3051+ } ) ;
3052+
3053+ it ( 'returns input unchanged when no tag filters are set' , async ( ) => {
3054+ const m = buildMaestro ( ) ;
3055+ const input = [ smokeFlow , flakyFlow , untaggedFlow ] ;
3056+ const result = await m [ 'filterFlowsByTags' ] ( input , projectDir ) ;
3057+ expect ( result ) . toBe ( input ) ;
3058+ } ) ;
3059+
3060+ it ( 'keeps only flows matching --include-tags' , async ( ) => {
3061+ const m = buildMaestro ( [ 'smoke' ] ) ;
3062+ const result = await m [ 'filterFlowsByTags' ] (
3063+ [ smokeFlow , flakyFlow , untaggedFlow ] ,
3064+ projectDir ,
3065+ ) ;
3066+ expect ( result ) . toEqual ( [ smokeFlow ] ) ;
3067+ } ) ;
3068+
3069+ it ( 'drops flows matching --exclude-tags' , async ( ) => {
3070+ const m = buildMaestro ( undefined , [ 'flaky' ] ) ;
3071+ const result = await m [ 'filterFlowsByTags' ] (
3072+ [ smokeFlow , flakyFlow , untaggedFlow ] ,
3073+ projectDir ,
3074+ ) ;
3075+ expect ( result ) . toEqual ( [ smokeFlow , untaggedFlow ] ) ;
3076+ } ) ;
3077+
3078+ it ( 'combines --include-tags and --exclude-tags' , async ( ) => {
3079+ const m = buildMaestro ( [ 'smoke' ] , [ 'flaky' ] ) ;
3080+ const result = await m [ 'filterFlowsByTags' ] (
3081+ [ smokeFlow , flakyFlow , untaggedFlow ] ,
3082+ projectDir ,
3083+ ) ;
3084+ expect ( result ) . toEqual ( [ smokeFlow ] ) ;
3085+ } ) ;
3086+
3087+ it ( 'always preserves config files' , async ( ) => {
3088+ const m = buildMaestro ( [ 'smoke' ] ) ;
3089+ const result = await m [ 'filterFlowsByTags' ] (
3090+ [ smokeFlow , flakyFlow , configFile ] ,
3091+ projectDir ,
3092+ ) ;
3093+ expect ( result ) . toContain ( configFile ) ;
3094+ expect ( result ) . toContain ( smokeFlow ) ;
3095+ expect ( result ) . not . toContain ( flakyFlow ) ;
3096+ } ) ;
3097+
3098+ it ( 'drops dependencies orphaned by filtered-out flows' , async ( ) => {
3099+ const m = buildMaestro ( [ 'smoke' ] ) ;
3100+ const result = await m [ 'filterFlowsByTags' ] (
3101+ [ flowWithDep , flowWithOrphanDep , helperScript , orphanScript ] ,
3102+ projectDir ,
3103+ ) ;
3104+ expect ( result ) . toContain ( flowWithDep ) ;
3105+ expect ( result ) . toContain ( helperScript ) ;
3106+ expect ( result ) . not . toContain ( flowWithOrphanDep ) ;
3107+ expect ( result ) . not . toContain ( orphanScript ) ;
3108+ } ) ;
3109+
3110+ it ( 'throws TestingBotError when no flows match the filters' , async ( ) => {
3111+ const m = buildMaestro ( [ 'nonexistent' ] ) ;
3112+ await expect (
3113+ m [ 'filterFlowsByTags' ] ( [ smokeFlow , flakyFlow ] , projectDir ) ,
3114+ ) . rejects . toThrow ( TestingBotError ) ;
3115+ } ) ;
3116+ } ) ;
3117+
30093118 describe ( 'Flow Status Display' , ( ) => {
30103119 describe ( 'getFlowStatusDisplay' , ( ) => {
30113120 it ( 'should return white WAITING for WAITING status' , ( ) => {
0 commit comments