Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/only-develop-to-main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: only-develop-to-main

on:
pull_request:
branches: ["main"]

jobs:
enforce:
runs-on: ubuntu-latest
steps:
- name: Allow only develop -> main PRs
run: |
echo "head_ref: ${{ github.head_ref }}"
echo "base_ref: ${{ github.base_ref }}"
if [ "${{ github.base_ref }}" = "main" ] && [ "${{ github.head_ref }}" != "develop" ]; then
echo "Only PRs from 'develop' to 'main' are allowed."
exit 1
fi
39 changes: 39 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Quality

on:
pull_request:
branches: ["develop"]

jobs:
quality:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Dart
uses: dart-lang/setup-dart@v1
with:
sdk: stable

- name: Cache pub
uses: actions/cache@v4
with:
path: |
~/.pub-cache
.dart_tool
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
restore-keys: |
${{ runner.os }}-pub-

- name: Install dependencies
run: dart pub get

- name: Check formatting
run: dart format --set-exit-if-changed .

- name: Static analysis
run: dart analyze

- name: pub.dev dry-run
run: dart pub publish --dry-run
33 changes: 33 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Test

on:
pull_request:
branches: ["develop"]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Dart
uses: dart-lang/setup-dart@v1
with:
sdk: stable

- name: Cache pub
uses: actions/cache@v4
with:
path: |
~/.pub-cache
.dart_tool
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
restore-keys: |
${{ runner.os }}-pub-

- name: Install dependencies
run: dart pub get

- name: Run tests
run: dart test
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.6.0
- **BREAKING**: Removed `requestedPermissions` field from `AppInfo` class to improve performance and reduce memory usage
- Added new API: `getRequestedPermissions(String packageName)` to fetch app permissions on demand
- Added comprehensive unit tests for `AppInfo`, `AppChangeType`, and `AppChangeEvent` classes

## 0.5.1
- Expanded `AppInfo` with additional Android-facing fields: `category`, `targetSdkVersion`, `minSdkVersion`, `enabled`, `processName`, `installLocation`, `requestedPermissions`.

Expand Down
5 changes: 3 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_device_apps_platform_interface
description: Platform-agnostic API contract for flutter_device_apps (federated).
version: 0.5.1
version: 0.6.0
repository: https://github.com/okmsbun/flutter_device_apps_platform_interface
issue_tracker: https://github.com/okmsbun/flutter_device_apps_platform_interface/issues
topics:
Expand All @@ -17,4 +17,5 @@ dependencies:
plugin_platform_interface: ^2.1.8

dev_dependencies:
lints: ^6.0.0
lints: ^6.1.0
test: ^1.29.0
263 changes: 263 additions & 0 deletions test/app_change_event_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import 'package:flutter_device_apps_platform_interface/flutter_device_apps_app_change_event.dart';
import 'package:test/test.dart';

void main() {
group('AppChangeType', () {
test('has all expected values', () {
expect(AppChangeType.values, hasLength(3));
expect(AppChangeType.values, contains(AppChangeType.installed));
expect(AppChangeType.values, contains(AppChangeType.removed));
expect(AppChangeType.values, contains(AppChangeType.updated));
});

test('enum names match expected strings', () {
expect(AppChangeType.installed.name, 'installed');
expect(AppChangeType.removed.name, 'removed');
expect(AppChangeType.updated.name, 'updated');
});
});

group('AppChangeEvent', () {
group('constructor', () {
test('creates instance with all null values', () {
const event = AppChangeEvent();

expect(event.packageName, isNull);
expect(event.type, isNull);
expect(event.isReplacing, isNull);
});

test('creates instance with all values', () {
const event = AppChangeEvent(
packageName: 'com.example.app',
type: AppChangeType.installed,
isReplacing: false,
);

expect(event.packageName, 'com.example.app');
expect(event.type, AppChangeType.installed);
expect(event.isReplacing, false);
});

test('creates instance with partial values', () {
const event = AppChangeEvent(
packageName: 'com.example.app',
);

expect(event.packageName, 'com.example.app');
expect(event.type, isNull);
expect(event.isReplacing, isNull);
});
});

group('fromMap', () {
test('parses empty map', () {
final event = AppChangeEvent.fromMap({});

expect(event.packageName, isNull);
expect(event.type, isNull);
expect(event.isReplacing, isNull);
});

test('parses packageName correctly', () {
final event = AppChangeEvent.fromMap({
'packageName': 'com.test.app',
});

expect(event.packageName, 'com.test.app');
});

test('parses type "installed" correctly', () {
final event = AppChangeEvent.fromMap({
'type': 'installed',
});

expect(event.type, AppChangeType.installed);
});

test('parses type "removed" correctly', () {
final event = AppChangeEvent.fromMap({
'type': 'removed',
});

expect(event.type, AppChangeType.removed);
});

test('parses type "updated" correctly', () {
final event = AppChangeEvent.fromMap({
'type': 'updated',
});

expect(event.type, AppChangeType.updated);
});

test('returns null type for invalid type string', () {
final event = AppChangeEvent.fromMap({
'type': 'unknown',
});

expect(event.type, isNull);
});

test('returns null type for empty type string', () {
final event = AppChangeEvent.fromMap({
'type': '',
});

expect(event.type, isNull);
});

test('parses isReplacing from bool value', () {
final eventTrue = AppChangeEvent.fromMap({
'isReplacing': true,
});
final eventFalse = AppChangeEvent.fromMap({
'isReplacing': false,
});

expect(eventTrue.isReplacing, true);
expect(eventFalse.isReplacing, false);
});

test('parses isReplacing from string value', () {
final eventTrue = AppChangeEvent.fromMap({
'isReplacing': 'true',
});
final eventFalse = AppChangeEvent.fromMap({
'isReplacing': 'false',
});

expect(eventTrue.isReplacing, true);
expect(eventFalse.isReplacing, false);
});

test('returns null isReplacing for invalid string', () {
final event = AppChangeEvent.fromMap({
'isReplacing': 'yes',
});

expect(event.isReplacing, isNull);
});

test('parses complete map with all fields', () {
final event = AppChangeEvent.fromMap({
'packageName': 'com.example.fullapp',
'type': 'updated',
'isReplacing': true,
});

expect(event.packageName, 'com.example.fullapp');
expect(event.type, AppChangeType.updated);
expect(event.isReplacing, true);
});
});

group('toMap', () {
test('converts empty event to map', () {
const event = AppChangeEvent();
final Map<String, Object?> map = event.toMap();

expect(map['packageName'], isNull);
expect(map['type'], isNull);
expect(map['isReplacing'], isNull);
});

test('converts full event to map', () {
const event = AppChangeEvent(
packageName: 'com.example.app',
type: AppChangeType.installed,
isReplacing: false,
);
final Map<String, Object?> map = event.toMap();

expect(map['packageName'], 'com.example.app');
expect(map['type'], 'installed');
expect(map['isReplacing'], false);
});

test('converts removed type correctly', () {
const event = AppChangeEvent(type: AppChangeType.removed);
final Map<String, Object?> map = event.toMap();

expect(map['type'], 'removed');
});

test('converts updated type correctly', () {
const event = AppChangeEvent(type: AppChangeType.updated);
final Map<String, Object?> map = event.toMap();

expect(map['type'], 'updated');
});

test('converts partial event to map', () {
const event = AppChangeEvent(
packageName: 'com.partial.app',
isReplacing: true,
);
final Map<String, Object?> map = event.toMap();

expect(map['packageName'], 'com.partial.app');
expect(map['type'], isNull);
expect(map['isReplacing'], true);
});
});

group('round-trip (fromMap -> toMap)', () {
test('installed event survives round-trip', () {
final originalMap = <String, Object?>{
'packageName': 'com.roundtrip.installed',
'type': 'installed',
'isReplacing': false,
};

final event = AppChangeEvent.fromMap(originalMap);
final Map<String, Object?> resultMap = event.toMap();

expect(resultMap['packageName'], originalMap['packageName']);
expect(resultMap['type'], originalMap['type']);
expect(resultMap['isReplacing'], originalMap['isReplacing']);
});

test('removed event survives round-trip', () {
final originalMap = <String, Object?>{
'packageName': 'com.roundtrip.removed',
'type': 'removed',
'isReplacing': false,
};

final event = AppChangeEvent.fromMap(originalMap);
final Map<String, Object?> resultMap = event.toMap();

expect(resultMap['packageName'], originalMap['packageName']);
expect(resultMap['type'], originalMap['type']);
expect(resultMap['isReplacing'], originalMap['isReplacing']);
});

test('updated event with replacing survives round-trip', () {
final originalMap = <String, Object?>{
'packageName': 'com.roundtrip.updated',
'type': 'updated',
'isReplacing': true,
};

final event = AppChangeEvent.fromMap(originalMap);
final Map<String, Object?> resultMap = event.toMap();

expect(resultMap['packageName'], originalMap['packageName']);
expect(resultMap['type'], originalMap['type']);
expect(resultMap['isReplacing'], originalMap['isReplacing']);
});

test('empty event survives round-trip', () {
final originalMap = <String, Object?>{};

final event = AppChangeEvent.fromMap(originalMap);
final Map<String, Object?> resultMap = event.toMap();

expect(resultMap['packageName'], isNull);
expect(resultMap['type'], isNull);
expect(resultMap['isReplacing'], isNull);
});
});
});
}
Loading
Loading