diff --git a/.github/scripts/detect-android-changes.cjs b/.github/scripts/detect-android-changes.cjs index 08d64f7..bb6f6ac 100644 --- a/.github/scripts/detect-android-changes.cjs +++ b/.github/scripts/detect-android-changes.cjs @@ -66,7 +66,6 @@ function classifyFiles(fileList) { if (!Array.isArray(fileList)) { return { android_changed: false, - native_build_changed: false, app_changed: false, native_build_changed: false, appFiles: [], @@ -147,7 +146,6 @@ function detectAndroidChanges({ eventName, baseSha, headSha, repoDir }, deps = { if (eventName === 'workflow_dispatch') { return { android_changed: true, - native_build_changed: true, app_changed: true, native_build_changed: true, reason: 'Manual workflow_dispatch; full pipeline authorized', diff --git a/.github/scripts/detect-android-changes.test.cjs b/.github/scripts/detect-android-changes.test.cjs index 75e8f67..fdf22f4 100644 --- a/.github/scripts/detect-android-changes.test.cjs +++ b/.github/scripts/detect-android-changes.test.cjs @@ -571,6 +571,113 @@ describe('Workflow Scenario Matrix Verification', () => { assert.strictEqual(result.android_changed, true); assert.strictEqual(result.app_changed, true); + assert.strictEqual(result.native_build_changed, true); + }); +}); + +describe('Release Job Gating (native_build_changed output)', () => { + const baseSha = '0000000000000000000000000000000000000001'; + const headSha = '0000000000000000000000000000000000000002'; + + it('Dart-only changes skip release job (native_build_changed=false)', () => { + const result = detectAndroidChanges({ + eventName: 'push', + baseSha, + headSha + }, { getChangedFilesFn: () => ['lib/main.dart', 'test/widget_test.dart', 'assets/icons/app.png'] }); + + assert.strictEqual(result.native_build_changed, false, 'Dart-only changes should skip release'); + assert.strictEqual(result.app_changed, true, 'app_changed should still be true'); + }); + + it('Android code changes trigger release job (native_build_changed=true)', () => { + const result = detectAndroidChanges({ + eventName: 'push', + baseSha, + headSha + }, { getChangedFilesFn: () => ['android/app/build.gradle'] }); + + assert.strictEqual(result.native_build_changed, true, 'Android changes should trigger release'); + }); + + it('CI-only changes (.github/) trigger release job (native_build_changed=true)', () => { + const result = detectAndroidChanges({ + eventName: 'push', + baseSha, + headSha + }, { getChangedFilesFn: () => ['.github/workflows/other.yml', '.github/scripts/helper.sh'] }); + + assert.strictEqual(result.native_build_changed, true, 'CI changes should trigger release'); + }); + + it('Config file changes (pubspec.yaml) trigger release job (native_build_changed=true)', () => { + const result = detectAndroidChanges({ + eventName: 'push', + baseSha, + headSha + }, { getChangedFilesFn: () => ['pubspec.yaml'] }); + + assert.strictEqual(result.native_build_changed, true, 'pubspec.yaml changes should trigger release'); + }); + + it('Build config changes (build.yaml) trigger release job (native_build_changed=true)', () => { + const result = detectAndroidChanges({ + eventName: 'push', + baseSha, + headSha + }, { getChangedFilesFn: () => ['build.yaml'] }); + + assert.strictEqual(result.native_build_changed, true, 'build.yaml changes should trigger release'); + }); + + it('Mixed Dart and Android changes trigger release job (native_build_changed=true)', () => { + const result = detectAndroidChanges({ + eventName: 'push', + baseSha, + headSha + }, { getChangedFilesFn: () => ['lib/main.dart', 'android/app/build.gradle'] }); + + assert.strictEqual(result.native_build_changed, true, 'Mixed changes should trigger release'); + }); + + it('Manual dispatch always triggers release job (native_build_changed=true)', () => { + const result = detectAndroidChanges({ + eventName: 'workflow_dispatch', + baseSha: '', + headSha: '' + }); + + assert.strictEqual(result.native_build_changed, true, 'Manual dispatch should always trigger release'); + }); + + it('Invalid base SHA fails safe to trigger release (native_build_changed=true)', () => { + const result = detectAndroidChanges({ + eventName: 'push', + baseSha: 'invalid', + headSha + }, { getChangedFilesFn: () => ['lib/main.dart'] }); + + assert.strictEqual(result.native_build_changed, true, 'Invalid SHA should fail safe to trigger release'); + }); + + it('Unrelated changes (docs/, website/) skip release job (native_build_changed=false)', () => { + const result = detectAndroidChanges({ + eventName: 'push', + baseSha, + headSha + }, { getChangedFilesFn: () => ['docs/readme.md', 'website/index.html'] }); + + assert.strictEqual(result.native_build_changed, false, 'Unrelated changes should skip release'); + }); + + it('dependency changes (pubspec.lock) trigger release job (native_build_changed=true)', () => { + const result = detectAndroidChanges({ + eventName: 'push', + baseSha, + headSha + }, { getChangedFilesFn: () => ['pubspec.lock'] }); + + assert.strictEqual(result.native_build_changed, true, 'pubspec.lock changes should trigger release'); }); });