From 981382c8fe8273e20aceb7fcd02880b9cdfca0eb Mon Sep 17 00:00:00 2001 From: revan Date: Thu, 21 Nov 2024 12:08:00 +0100 Subject: [PATCH 1/5] added confing plugins to make it easier to the users --- app.plugin.js | 11 +++++++ .../withMotionActivityPermissions.ts | 30 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 app.plugin.js create mode 100644 config-plugin/withMotionActivityPermissions.ts diff --git a/app.plugin.js b/app.plugin.js new file mode 100644 index 0000000..c2c82a8 --- /dev/null +++ b/app.plugin.js @@ -0,0 +1,11 @@ +const { createRunOncePlugin } = require("expo/config-plugins"); + +const withMotionActivityPermissions = + require("./config-plugin/withMotionActivityPermissions").default; +const pkg = require("./package.json"); + +module.exports = createRunOncePlugin( + withMotionActivityPermissions, + pkg.name, + pkg.version, +); diff --git a/config-plugin/withMotionActivityPermissions.ts b/config-plugin/withMotionActivityPermissions.ts new file mode 100644 index 0000000..38e220f --- /dev/null +++ b/config-plugin/withMotionActivityPermissions.ts @@ -0,0 +1,30 @@ +import { + withInfoPlist, + withAndroidManifest, + ConfigPlugin, +} from "@expo/config-plugins"; + +const withMotionActivityPermissions: ConfigPlugin = (config) => { + // Add iOS motion activity permissions + config = withInfoPlist(config, (config) => { + config.modResults.NSMotionUsageDescription = + config.modResults.NSMotionUsageDescription || + "This app uses motion activity tracking."; + return config; + }); + + // Add Android Activity Recognition permission + config = withAndroidManifest(config, (config) => { + const androidManifest = config.modResults; + const permissions = androidManifest.manifest["uses-permission"] || []; + permissions.push({ + $: { "android:name": "android.permission.ACTIVITY_RECOGNITION" }, + }); + androidManifest.manifest["uses-permission"] = permissions; + return config; + }); + + return config; +}; + +export default withMotionActivityPermissions; From 6de81b0d15c2e8b1fa12bbfaf5d3c1c0ce0d511c Mon Sep 17 00:00:00 2001 From: revan Date: Thu, 21 Nov 2024 12:42:32 +0100 Subject: [PATCH 2/5] modified the readme to reflect the new plugins and added license --- LICENSE | 21 +++++++++++++++++++++ README.md | 33 ++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2bb2435 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [2024] [Kingstinct] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 1aa4207..657a161 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,33 @@ A React Native library to access and track motion activity data using Apple's Co { "expo": { - "ios": { - "infoPlist": { - "NSMotionUsageDescription": "This app uses motion data to track your activity." - } - }, + "plugins": [ + "react-native-motion-activity-tracker" + ] } } + ``` +3. If you want to customize the iOS permission description, you can configure it like this: + { + "expo": { + "plugins": [ + [ + "react-native-motion-activity-tracker", + { + "NSMotionUsageDescription": "This app uses motion data to enhance your experience." + } + ] + ] + } +} + +4. After making these changes, rebuild your app to apply the native changes: + + ```bash + expo prebuild + ``` + # Examples @@ -199,6 +218,10 @@ Checks the motion activity authorization status. Returns one of the following st - `DENIED` - `AUTHORIZED` +## License + +This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details. + ## Contributing Contributions are welcome! From 817f75ca6c9d08e839b0801a691b958287b4c604 Mon Sep 17 00:00:00 2001 From: RevanToma Date: Fri, 22 Nov 2024 10:56:39 +0100 Subject: [PATCH 3/5] updated the readme test link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 657a161..07eb16e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # react-native-motion-activity-tracker -[![Test Status](https://github.com/YourGithubUsername/react-native-motion-activity-tracker/actions/workflows/test.yml/badge.svg)](https://github.com/YourGithubUsername/react-native-motion-activity-tracker/actions/workflows/test.yml) +[![Test Status](https://github.com/Kingstinct/react-native-motion-activity-tracker/actions/workflows/test.yml/badge.svg)](https://github.com/Kingstinct/react-native-motion-activity-tracker/actions/workflows/test.yml) [![Latest version on NPM](https://img.shields.io/npm/v/react-native-motion-activity-tracker)](https://www.npmjs.com/package/react-native-motion-activity-tracker) [![Discord](https://dcbadge.vercel.app/api/server/5wQGsRfS?style=flat)](https://discord.gg/5wQGsRfS) From 46557831a734cc8b1c7c09b498d96a511f04b91d Mon Sep 17 00:00:00 2001 From: RevanToma Date: Fri, 22 Nov 2024 12:36:08 +0100 Subject: [PATCH 4/5] added tests for the typescript functions and the plugin, added jest configs --- .github/workflows/test.yml | 3 + jest.config.js | 16 ++++ package-lock.json | 8 +- tests/MotionActivityTracker.test.ts | 43 ++++++++++ tests/withMotionActivityPermissions.test.ts | 93 +++++++++++++++++++++ tsconfig.json | 2 +- 6 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 jest.config.js create mode 100644 tests/MotionActivityTracker.test.ts create mode 100644 tests/withMotionActivityPermissions.test.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 27a6a1f..5eb6f4a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,3 +28,6 @@ jobs: - name: Run typecheck run: npm run typecheck + + - name: Run tests + run: npm test diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..9abace0 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,16 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + testMatch: ['**/tests/**/*.test.ts', '**/tests/**/*.test.tsx'], + transformIgnorePatterns: ['/node_modules/'], + globals: { + 'ts-jest': { + tsconfig: 'tsconfig.json', + }, + }, + }; + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 016ee89..1f2da28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "motion-activity-tracker", - "version": "0.1.1", + "name": "react-native-motion-activity-tracker", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "motion-activity-tracker", - "version": "0.1.1", + "name": "react-native-motion-activity-tracker", + "version": "0.1.3", "license": "MIT", "devDependencies": { "@types/react": "^18.0.25", diff --git a/tests/MotionActivityTracker.test.ts b/tests/MotionActivityTracker.test.ts new file mode 100644 index 0000000..b3c9a13 --- /dev/null +++ b/tests/MotionActivityTracker.test.ts @@ -0,0 +1,43 @@ +const MotionActivityTracker = require('../src/MotionActivityTrackerModule'); + + +jest.mock('../src/MotionActivityTrackerModule.ts', () => ({ + startTracking: jest.fn(() => Promise.resolve('tracking started')), + stopTracking: jest.fn(() => 'tracking stopped'), + getHistoricalDataIos: jest.fn((startDate, endDate) => + Promise.resolve([ + { walking: true, running: false, timestamp: 123456789, confidence: 'high' }, + ]), + ), + addMotionStateChangeListener: jest.fn((callback) => { + callback({ state: 'walking' }); + return { remove: jest.fn() }; + }), +})); + +describe('MotionActivityTracker', () => { + test('should start tracking', async () => { + const result = await MotionActivityTracker.startTracking(); + expect(result).toBe('tracking started'); + }); + + test('should stop tracking', () => { + const result = MotionActivityTracker.stopTracking(); + expect(result).toBe('tracking stopped'); + }); + + test('should fetch historical data', async () => { + const startDate = new Date(Date.now() - 1000 * 60 * 60); // 1 hour ago + const endDate = new Date(); + const data = await MotionActivityTracker.getHistoricalDataIos(startDate, endDate); + expect(data).toHaveLength(1); + expect(data[0]).toHaveProperty('walking', true); + }); + + test('should call listener on state change', () => { + const mockListener = jest.fn(); + const subscription = MotionActivityTracker.addMotionStateChangeListener(mockListener); + expect(mockListener).toHaveBeenCalledWith({ state: 'walking' }); + subscription.remove(); + }); +}); diff --git a/tests/withMotionActivityPermissions.test.ts b/tests/withMotionActivityPermissions.test.ts new file mode 100644 index 0000000..4385f6a --- /dev/null +++ b/tests/withMotionActivityPermissions.test.ts @@ -0,0 +1,93 @@ +import { withInfoPlist, withAndroidManifest } from "@expo/config-plugins"; +import withMotionActivityPermissions from "../config-plugin/withMotionActivityPermissions"; +import type { ExpoConfig } from "@expo/config-types"; + +interface ExpoConfigWithModResults extends ExpoConfig { + modResults?: { + // For iOS + NSMotionUsageDescription?: string; + // For Android + manifest?: { + "uses-permission"?: Array<{ $: { "android:name": string } }>; + }; + }; +} + +jest.mock("@expo/config-plugins", () => ({ + withInfoPlist: jest.fn((config, callback) => { + const updatedConfig = { + ...config, + modResults: { + ...config.modResults, + NSMotionUsageDescription: undefined, + }, + }; + const modifiedConfig = callback(updatedConfig); + return { + ...config, + modResults: { ...updatedConfig.modResults, ...modifiedConfig.modResults }, + }; + }), + withAndroidManifest: jest.fn((config, callback) => { + const updatedConfig = { + ...config, + modResults: { + ...config.modResults, + manifest: { + ...config.modResults?.manifest, + "uses-permission": [], + }, + }, + }; + const modifiedConfig = callback(updatedConfig); + return { + ...config, + modResults: { ...updatedConfig.modResults, ...modifiedConfig.modResults }, + }; + }), +})); + +describe("withMotionActivityPermissions", () => { + it("should add iOS motion activity permissions", () => { + const config: ExpoConfigWithModResults = { + name: "mock-app", + slug: "mock-app", + modResults: { + NSMotionUsageDescription: undefined, + }, + }; + + const updatedConfig: ExpoConfigWithModResults = + withMotionActivityPermissions(config); + + expect(updatedConfig.modResults?.NSMotionUsageDescription).toBe( + "This app uses motion activity tracking." + ); + + expect(withInfoPlist).toHaveBeenCalled(); + }); + + it("should add Android motion activity recognition permission", () => { + const config: ExpoConfigWithModResults = { + name: "mock-app", + slug: "mock-app", + modResults: { + manifest: { + "uses-permission": [], + }, + }, + }; + + const updatedConfig: ExpoConfigWithModResults = + withMotionActivityPermissions(config); + + const permissions = updatedConfig.modResults?.manifest?.["uses-permission"]; + expect(permissions).toEqual( + expect.arrayContaining([ + { $: { "android:name": "android.permission.ACTIVITY_RECOGNITION" } }, + ]) + ); + + expect(withAndroidManifest).toHaveBeenCalled(); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index a24ec0f..e267900 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,6 @@ "compilerOptions": { "outDir": "./build" }, - "include": ["./src"], + "include": ["./src", "./tests"], "exclude": ["**/__mocks__/*", "**/__tests__/*"] } From cc6c3545b54de27cb45f4f19f22ce3a94bd679ba Mon Sep 17 00:00:00 2001 From: revan Date: Fri, 22 Nov 2024 13:25:45 +0100 Subject: [PATCH 5/5] added a check before adding it to the manifest and updated the test --- .../withMotionActivityPermissions.ts | 18 ++++++-- tests/withMotionActivityPermissions.test.ts | 44 ++++++++++++++++--- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/config-plugin/withMotionActivityPermissions.ts b/config-plugin/withMotionActivityPermissions.ts index 38e220f..ce55a43 100644 --- a/config-plugin/withMotionActivityPermissions.ts +++ b/config-plugin/withMotionActivityPermissions.ts @@ -17,9 +17,21 @@ const withMotionActivityPermissions: ConfigPlugin = (config) => { config = withAndroidManifest(config, (config) => { const androidManifest = config.modResults; const permissions = androidManifest.manifest["uses-permission"] || []; - permissions.push({ - $: { "android:name": "android.permission.ACTIVITY_RECOGNITION" }, - }); + + // Check if the permission already exists + const hasActivityRecognitionPermission = permissions.some( + (permission) => + permission.$["android:name"] === + "android.permission.ACTIVITY_RECOGNITION", + ); + + // Add the permission only if it doesn't exist + if (!hasActivityRecognitionPermission) { + permissions.push({ + $: { "android:name": "android.permission.ACTIVITY_RECOGNITION" }, + }); + } + androidManifest.manifest["uses-permission"] = permissions; return config; }); diff --git a/tests/withMotionActivityPermissions.test.ts b/tests/withMotionActivityPermissions.test.ts index 4385f6a..e6bf495 100644 --- a/tests/withMotionActivityPermissions.test.ts +++ b/tests/withMotionActivityPermissions.test.ts @@ -1,14 +1,15 @@ import { withInfoPlist, withAndroidManifest } from "@expo/config-plugins"; -import withMotionActivityPermissions from "../config-plugin/withMotionActivityPermissions"; import type { ExpoConfig } from "@expo/config-types"; +import withMotionActivityPermissions from "../config-plugin/withMotionActivityPermissions"; + interface ExpoConfigWithModResults extends ExpoConfig { modResults?: { // For iOS NSMotionUsageDescription?: string; // For Android manifest?: { - "uses-permission"?: Array<{ $: { "android:name": string } }>; + "uses-permission"?: { $: { "android:name": string } }[]; }; }; } @@ -19,7 +20,7 @@ jest.mock("@expo/config-plugins", () => ({ ...config, modResults: { ...config.modResults, - NSMotionUsageDescription: undefined, + NSMotionUsageDescription: undefined, }, }; const modifiedConfig = callback(updatedConfig); @@ -35,7 +36,7 @@ jest.mock("@expo/config-plugins", () => ({ ...config.modResults, manifest: { ...config.modResults?.manifest, - "uses-permission": [], + "uses-permission": [], }, }, }; @@ -61,13 +62,13 @@ describe("withMotionActivityPermissions", () => { withMotionActivityPermissions(config); expect(updatedConfig.modResults?.NSMotionUsageDescription).toBe( - "This app uses motion activity tracking." + "This app uses motion activity tracking.", ); expect(withInfoPlist).toHaveBeenCalled(); }); - it("should add Android motion activity recognition permission", () => { + it("should add Android motion activity recognition permission if it does not exist", () => { const config: ExpoConfigWithModResults = { name: "mock-app", slug: "mock-app", @@ -85,7 +86,36 @@ describe("withMotionActivityPermissions", () => { expect(permissions).toEqual( expect.arrayContaining([ { $: { "android:name": "android.permission.ACTIVITY_RECOGNITION" } }, - ]) + ]), + ); + + expect(withAndroidManifest).toHaveBeenCalled(); + }); + + it("should not add duplicate Android motion activity recognition permission", () => { + const config: ExpoConfigWithModResults = { + name: "mock-app", + slug: "mock-app", + modResults: { + manifest: { + "uses-permission": [ + { + $: { "android:name": "android.permission.ACTIVITY_RECOGNITION" }, + }, + ], + }, + }, + }; + + const updatedConfig: ExpoConfigWithModResults = + withMotionActivityPermissions(config); + + const permissions = updatedConfig.modResults?.manifest?.["uses-permission"]; + expect(permissions).toHaveLength(1); // Ensure no duplicates are added + expect(permissions).toEqual( + expect.arrayContaining([ + { $: { "android:name": "android.permission.ACTIVITY_RECOGNITION" } }, + ]), ); expect(withAndroidManifest).toHaveBeenCalled();