From cf6b7e6d3b2b25e0d7ef8070c08f1004b9e2b1fe Mon Sep 17 00:00:00 2001 From: Lewis Casewell Date: Mon, 9 Jun 2025 22:24:57 +0100 Subject: [PATCH 1/4] feat: Add check for expo-notifications notification icon configurations --- .../plugin/__tests__/androidPlugin.test.ts | 45 ++++++++++++++++++- .../__tests__/fixtures/expo-config-example.ts | 23 +++++++--- .../android/setupFirebaseNotifationIcon.ts | 32 +++++++++++-- 3 files changed, 89 insertions(+), 11 deletions(-) diff --git a/packages/messaging/plugin/__tests__/androidPlugin.test.ts b/packages/messaging/plugin/__tests__/androidPlugin.test.ts index f2c333212b..12f3b380c0 100644 --- a/packages/messaging/plugin/__tests__/androidPlugin.test.ts +++ b/packages/messaging/plugin/__tests__/androidPlugin.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from '@jest/globals'; import { setFireBaseMessagingAndroidManifest } from '../src/android/setupFirebaseNotifationIcon'; import { ExpoConfig } from '@expo/config-types'; -import expoConfigExample from './fixtures/expo-config-example'; +import { expoConfigExample, expoConfigExampleWithExpoNotificationsPlugin } from './fixtures/expo-config-example'; import manifestApplicationExample from './fixtures/application-example'; import { ManifestApplication } from '@expo/config-plugins/build/android/Manifest'; @@ -67,4 +67,47 @@ describe('Config Plugin Android Tests', function () { // eslint-disable-next-line no-console console.warn = warnOrig; }); + + it('applies changes to app/src/main/AndroidManifest.xml with expo-notifications plugin config when app.json notification is undefined', async function () { + const config: ExpoConfig = JSON.parse(JSON.stringify(expoConfigExampleWithExpoNotificationsPlugin)); + const manifestApplication: ManifestApplication = JSON.parse( + JSON.stringify(manifestApplicationExample), + ); + config.notification = undefined; + setFireBaseMessagingAndroidManifest(config, manifestApplication); + expect(manifestApplication['meta-data']).toContainEqual({ + $: { + 'android:name': 'com.google.firebase.messaging.default_notification_icon', + 'android:resource': '@drawable/notification_icon', + }, + }); + expect(manifestApplication['meta-data']).toContainEqual({ + $: { + 'android:name': 'com.google.firebase.messaging.default_notification_color', + 'android:resource': '@color/notification_icon_color', + 'tools:replace': 'android:resource', + }, + }); + }); + + it('applies changes to app/src/main/AndroidManifest.xml with app.json notification config when both configs are defined', async function () { + const config: ExpoConfig = JSON.parse(JSON.stringify(expoConfigExampleWithExpoNotificationsPlugin)); + const manifestApplication: ManifestApplication = JSON.parse( + JSON.stringify(manifestApplicationExample), + ); + setFireBaseMessagingAndroidManifest(config, manifestApplication); + expect(manifestApplication['meta-data']).toContainEqual({ + $: { + 'android:name': 'com.google.firebase.messaging.default_notification_icon', + 'android:resource': '@drawable/notification_icon', + }, + }); + expect(manifestApplication['meta-data']).toContainEqual({ + $: { + 'android:name': 'com.google.firebase.messaging.default_notification_color', + 'android:resource': '@color/notification_icon_color', + 'tools:replace': 'android:resource', + }, + }); + }); }); diff --git a/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts b/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts index c9aa9e1e27..6c925336c3 100644 --- a/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts +++ b/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts @@ -1,15 +1,26 @@ import { ExpoConfig } from '@expo/config-types'; +const notificationConfig = { + icon: 'IconAsset', + color: '#1D172D', +}; + +/** + * @type {import('@expo/config-types').ExpoConfig} + */ +export const expoConfigExample: ExpoConfig = { + name: 'FirebaseMessagingTest', + slug: 'fire-base-messaging-test', + notification: notificationConfig, +}; + /** * @type {import('@expo/config-types').ExpoConfig} */ -const expoConfigExample: ExpoConfig = { +export const expoConfigExampleWithExpoNotificationsPlugin: ExpoConfig = { name: 'FirebaseMessagingTest', slug: 'fire-base-messaging-test', - notification: { - icon: 'IconAsset', - color: '#1D172D', - }, + notification: notificationConfig, + plugins: [['expo-notifications', notificationConfig]], }; -export default expoConfigExample; diff --git a/packages/messaging/plugin/src/android/setupFirebaseNotifationIcon.ts b/packages/messaging/plugin/src/android/setupFirebaseNotifationIcon.ts index 7a226e7a53..5326660281 100644 --- a/packages/messaging/plugin/src/android/setupFirebaseNotifationIcon.ts +++ b/packages/messaging/plugin/src/android/setupFirebaseNotifationIcon.ts @@ -26,12 +26,36 @@ export const withExpoPluginFirebaseNotification: ConfigPlugin = config => { }); }; +interface NotificationConfig { + icon: string | null; + color: string | null; +} + export function setFireBaseMessagingAndroidManifest( config: ExpoConfig, application: ManifestApplication, ) { + const notificationConfig: NotificationConfig = { + icon: null, + color: null, + }; + + const notificationConfigFromPlugin = config.plugins?.find(plugin => plugin[0] === 'expo-notifications')?.[1]; + + // check if the notification config is defined in the expo-notifications plugin first + if (notificationConfigFromPlugin?.icon || notificationConfigFromPlugin?.color) { + notificationConfig.icon = notificationConfigFromPlugin?.icon || null; + notificationConfig.color = notificationConfigFromPlugin?.color || null; + } + + // then check if the notification config is defined in the app.json notification object and override the plugin config if it is defined + if (config.notification) { + notificationConfig.icon = config.notification.icon || notificationConfig?.icon || null; + notificationConfig.color = config.notification.color || notificationConfig?.color || null; + } + // If the notification object is not defined, print a friendly warning - if (!config.notification) { + if (!notificationConfig.icon && !notificationConfig.color) { // This warning is important because the notification icon can only use pure white on Android. By default, the system uses the app icon as the notification icon, but the app icon is usually not pure white, so you need to set the notification icon // eslint-disable-next-line no-console console.warn( @@ -46,10 +70,10 @@ export function setFireBaseMessagingAndroidManifest( const metaData = application['meta-data']; if ( - config.notification.icon && + notificationConfig.icon && !hasMetaData(application, 'com.google.firebase.messaging.default_notification_icon') ) { - // Expo will automatically create '@drawable/notification_icon' resource if you specify config.notification.icon. + // Expo will automatically create '@drawable/notification_icon' resource if you specify config.notification.icon or expo-notifications plugin.icon. metaData.push({ $: { 'android:name': 'com.google.firebase.messaging.default_notification_icon', @@ -59,7 +83,7 @@ export function setFireBaseMessagingAndroidManifest( } if ( - config.notification.color && + notificationConfig.color && !hasMetaData(application, 'com.google.firebase.messaging.default_notification_color') ) { metaData.push({ From 01025cb326c2043deca92a6e32f4406d1fae8274 Mon Sep 17 00:00:00 2001 From: Lewis Casewell Date: Mon, 9 Jun 2025 22:39:49 +0100 Subject: [PATCH 2/4] fix: linting issues --- .../plugin/__tests__/androidPlugin.test.ts | 15 +++++++++++---- .../__tests__/fixtures/expo-config-example.ts | 1 - .../src/android/setupFirebaseNotifationIcon.ts | 4 +++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/messaging/plugin/__tests__/androidPlugin.test.ts b/packages/messaging/plugin/__tests__/androidPlugin.test.ts index 12f3b380c0..428003d261 100644 --- a/packages/messaging/plugin/__tests__/androidPlugin.test.ts +++ b/packages/messaging/plugin/__tests__/androidPlugin.test.ts @@ -1,7 +1,10 @@ import { describe, expect, it } from '@jest/globals'; import { setFireBaseMessagingAndroidManifest } from '../src/android/setupFirebaseNotifationIcon'; import { ExpoConfig } from '@expo/config-types'; -import { expoConfigExample, expoConfigExampleWithExpoNotificationsPlugin } from './fixtures/expo-config-example'; +import { + expoConfigExample, + expoConfigExampleWithExpoNotificationsPlugin, +} from './fixtures/expo-config-example'; import manifestApplicationExample from './fixtures/application-example'; import { ManifestApplication } from '@expo/config-plugins/build/android/Manifest'; @@ -69,7 +72,9 @@ describe('Config Plugin Android Tests', function () { }); it('applies changes to app/src/main/AndroidManifest.xml with expo-notifications plugin config when app.json notification is undefined', async function () { - const config: ExpoConfig = JSON.parse(JSON.stringify(expoConfigExampleWithExpoNotificationsPlugin)); + const config: ExpoConfig = JSON.parse( + JSON.stringify(expoConfigExampleWithExpoNotificationsPlugin), + ); const manifestApplication: ManifestApplication = JSON.parse( JSON.stringify(manifestApplicationExample), ); @@ -91,7 +96,9 @@ describe('Config Plugin Android Tests', function () { }); it('applies changes to app/src/main/AndroidManifest.xml with app.json notification config when both configs are defined', async function () { - const config: ExpoConfig = JSON.parse(JSON.stringify(expoConfigExampleWithExpoNotificationsPlugin)); + const config: ExpoConfig = JSON.parse( + JSON.stringify(expoConfigExampleWithExpoNotificationsPlugin), + ); const manifestApplication: ManifestApplication = JSON.parse( JSON.stringify(manifestApplicationExample), ); @@ -108,6 +115,6 @@ describe('Config Plugin Android Tests', function () { 'android:resource': '@color/notification_icon_color', 'tools:replace': 'android:resource', }, - }); + }); }); }); diff --git a/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts b/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts index 6c925336c3..6b726705d4 100644 --- a/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts +++ b/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts @@ -23,4 +23,3 @@ export const expoConfigExampleWithExpoNotificationsPlugin: ExpoConfig = { notification: notificationConfig, plugins: [['expo-notifications', notificationConfig]], }; - diff --git a/packages/messaging/plugin/src/android/setupFirebaseNotifationIcon.ts b/packages/messaging/plugin/src/android/setupFirebaseNotifationIcon.ts index 5326660281..f5cd267c62 100644 --- a/packages/messaging/plugin/src/android/setupFirebaseNotifationIcon.ts +++ b/packages/messaging/plugin/src/android/setupFirebaseNotifationIcon.ts @@ -40,7 +40,9 @@ export function setFireBaseMessagingAndroidManifest( color: null, }; - const notificationConfigFromPlugin = config.plugins?.find(plugin => plugin[0] === 'expo-notifications')?.[1]; + const notificationConfigFromPlugin = config.plugins?.find( + plugin => plugin[0] === 'expo-notifications', + )?.[1]; // check if the notification config is defined in the expo-notifications plugin first if (notificationConfigFromPlugin?.icon || notificationConfigFromPlugin?.color) { From b4be460335c5e227616809a5dc768a5e679aaeb8 Mon Sep 17 00:00:00 2001 From: Lewis Casewell Date: Mon, 9 Jun 2025 22:47:56 +0100 Subject: [PATCH 3/4] refactor: array check and rename fixture --- packages/messaging/plugin/__tests__/androidPlugin.test.ts | 6 +++--- .../plugin/__tests__/fixtures/expo-config-example.ts | 8 ++++++-- .../plugin/src/android/setupFirebaseNotifationIcon.ts | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/messaging/plugin/__tests__/androidPlugin.test.ts b/packages/messaging/plugin/__tests__/androidPlugin.test.ts index 428003d261..194016b8fe 100644 --- a/packages/messaging/plugin/__tests__/androidPlugin.test.ts +++ b/packages/messaging/plugin/__tests__/androidPlugin.test.ts @@ -3,7 +3,7 @@ import { setFireBaseMessagingAndroidManifest } from '../src/android/setupFirebas import { ExpoConfig } from '@expo/config-types'; import { expoConfigExample, - expoConfigExampleWithExpoNotificationsPlugin, + expoConfigExampleWithExpoNotificationsPlugins, } from './fixtures/expo-config-example'; import manifestApplicationExample from './fixtures/application-example'; import { ManifestApplication } from '@expo/config-plugins/build/android/Manifest'; @@ -73,7 +73,7 @@ describe('Config Plugin Android Tests', function () { it('applies changes to app/src/main/AndroidManifest.xml with expo-notifications plugin config when app.json notification is undefined', async function () { const config: ExpoConfig = JSON.parse( - JSON.stringify(expoConfigExampleWithExpoNotificationsPlugin), + JSON.stringify(expoConfigExampleWithExpoNotificationsPlugins), ); const manifestApplication: ManifestApplication = JSON.parse( JSON.stringify(manifestApplicationExample), @@ -97,7 +97,7 @@ describe('Config Plugin Android Tests', function () { it('applies changes to app/src/main/AndroidManifest.xml with app.json notification config when both configs are defined', async function () { const config: ExpoConfig = JSON.parse( - JSON.stringify(expoConfigExampleWithExpoNotificationsPlugin), + JSON.stringify(expoConfigExampleWithExpoNotificationsPlugins), ); const manifestApplication: ManifestApplication = JSON.parse( JSON.stringify(manifestApplicationExample), diff --git a/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts b/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts index 6b726705d4..c214371558 100644 --- a/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts +++ b/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts @@ -17,9 +17,13 @@ export const expoConfigExample: ExpoConfig = { /** * @type {import('@expo/config-types').ExpoConfig} */ -export const expoConfigExampleWithExpoNotificationsPlugin: ExpoConfig = { +export const expoConfigExampleWithExpoNotificationsPlugins: ExpoConfig = { name: 'FirebaseMessagingTest', slug: 'fire-base-messaging-test', notification: notificationConfig, - plugins: [['expo-notifications', notificationConfig]], + plugins: [ + ['expo-notifications', notificationConfig], + '../test-plugin', + ['expo-camera'], + ], }; diff --git a/packages/messaging/plugin/src/android/setupFirebaseNotifationIcon.ts b/packages/messaging/plugin/src/android/setupFirebaseNotifationIcon.ts index f5cd267c62..2ccac4ebbd 100644 --- a/packages/messaging/plugin/src/android/setupFirebaseNotifationIcon.ts +++ b/packages/messaging/plugin/src/android/setupFirebaseNotifationIcon.ts @@ -41,7 +41,7 @@ export function setFireBaseMessagingAndroidManifest( }; const notificationConfigFromPlugin = config.plugins?.find( - plugin => plugin[0] === 'expo-notifications', + plugin => Array.isArray(plugin) && plugin[0] === 'expo-notifications', )?.[1]; // check if the notification config is defined in the expo-notifications plugin first From 79eecee6a50d79da01337bb653d681a59ef49b80 Mon Sep 17 00:00:00 2001 From: Lewis Casewell Date: Fri, 11 Jul 2025 08:41:13 +0100 Subject: [PATCH 4/4] fix: lint issue --- .../plugin/__tests__/fixtures/expo-config-example.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts b/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts index c214371558..e89126f93d 100644 --- a/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts +++ b/packages/messaging/plugin/__tests__/fixtures/expo-config-example.ts @@ -21,9 +21,5 @@ export const expoConfigExampleWithExpoNotificationsPlugins: ExpoConfig = { name: 'FirebaseMessagingTest', slug: 'fire-base-messaging-test', notification: notificationConfig, - plugins: [ - ['expo-notifications', notificationConfig], - '../test-plugin', - ['expo-camera'], - ], + plugins: [['expo-notifications', notificationConfig], '../test-plugin', ['expo-camera']], };