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
63 changes: 63 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: tests

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
pest:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v5

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
coverage: none
tools: composer:v2

# nativephp/mobile v4 is not a stable Packagist release yet, and this
# suite needs its FakeBridge testing classes. Resolve the core from the
# public mobile-air repo's main branch (dev-main) — the same override
# test-plugins.sh applies locally. The default GITHUB_TOKEN is used only
# to avoid GitHub API rate limits while reading the public repo.
- name: Point Composer at the v4 core (dev-main)
run: |
composer config repositories.nativephp-mobile vcs https://github.com/nativephp/mobile-air
composer config minimum-stability dev
composer config prefer-stable true
composer config github-oauth.github.com "${{ secrets.GITHUB_TOKEN }}"
composer require "nativephp/mobile:dev-main" --no-update --no-interaction

- name: Install dependencies
run: composer update --prefer-dist --no-interaction --no-progress

- name: Run Pest
run: vendor/bin/pest

pint:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v5

# Pint is a standalone formatter — it needs no project vendor/, so this
# job skips composer entirely.
- name: Setup PHP with Pint
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
coverage: none
tools: pint

- name: Check code style
run: pint --test
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,40 @@ async function checkBeforeDownload() {
- Uses `NWPathMonitor` from Network framework
- `isConstrained` reflects Low Data Mode setting
- No special permissions required

## Testing

The plugin extends the NativePHP testing suite with network-specific helpers, so your app tests can fake connectivity and assert it was checked without knowing any bridge internals:

```php
use Native\Mobile\Testing\Native;

it('syncs everything on wifi', function () {
Native::fakeBridge()->withWifi();

Native::test(SyncButton::class)
->tap('Sync now')
->assertNetworkChecked();
});

it('warns instead of syncing when offline', function () {
Native::fakeBridge()->withOffline();

Native::test(SyncButton::class)
->tap('Sync now')
->assertSee('No connection');
});
```

### Helpers

- `withNetworkStatus(array $status = [])` — fake the raw response to `status()`. Accepts any of `connected`, `type`, `isExpensive`, `isConstrained`, `error` — the same fields the native bridge returns.
- `withWifi(array $extra = [])` — fake a connected, unmetered, unconstrained Wi-Fi status. Pass `$extra` to override individual fields.
- `withCellular(array $extra = [])` — fake a connected, metered cellular status.
- `withOffline(array $extra = [])` — fake a disconnected status (`type: 'unknown'`).
- `withError(string $error = 'Unknown error', array $extra = [])` — fake the native error path (e.g. an Android catch reporting failure to read connectivity state).
- `assertNetworkChecked()` — assert `status()` was called.

The helpers are available on `Native::fakeBridge()` and chain directly off `Native::test(...)`. They register automatically while running tests (requires a core with a macroable FakeBridge; on older cores they simply don't register).

Note that `status()` decodes the bridge's JSON response without the `true` flag, so it returns a `stdClass` object (`$status->type`), not an array — the same is true of the objects returned while faked.
13 changes: 13 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
cacheDirectory=".phpunit.cache"
>
<testsuites>
<testsuite name="Plugin">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
11 changes: 11 additions & 0 deletions src/NetworkServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use Illuminate\Support\ServiceProvider;
use Native\Mobile\Network;
use Native\Mobile\Providers\Testing\NetworkMacros;
use Native\Mobile\Testing\FakeBridge;

class NetworkServiceProvider extends ServiceProvider
{
Expand All @@ -12,5 +14,14 @@ public function register(): void
$this->app->singleton(Network::class, function () {
return new Network;
});

// Test sugar (withWifi(), assertNetworkChecked(), etc.) — only under
// a test runner, and only on a core whose FakeBridge is macroable
// (the method_exists guard keeps older v4 and v3 cores fatal-free).
if ($this->app->runningUnitTests()
&& class_exists(FakeBridge::class)
&& method_exists(FakeBridge::class, 'macro')) {
NetworkMacros::register();
}
}
}
94 changes: 94 additions & 0 deletions src/Testing/NetworkMacros.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

namespace Native\Mobile\Providers\Testing;

use Native\Mobile\Testing\FakeBridge;

/**
* Network test vocabulary for the NativePHP testing suite, registered as
* FakeBridge macros so app tests read in network terms instead of raw
* bridge method strings:
*
* Native::fakeBridge()->withWifi();
*
* Native::test(SyncButton::class)
* ->tap('sync')
* ->assertNetworkChecked();
*
* Network::status() decodes the raw JSON response with json_decode($result)
* (no `true` flag), so a scripted response comes back to the app as a
* stdClass, not an array — the shape mirrors the real bridge's fields:
* connected, type (wifi/cellular/ethernet/unknown/error), isExpensive,
* isConstrained, and error (only on the error path).
*
* Registered by NetworkServiceProvider when the app is running unit tests
* on a core whose FakeBridge supports macros.
*/
class NetworkMacros
{
public static function register(): void
{
/**
* Fake the response to Network::status(). Pass the raw response
* shape — connected, type, isExpensive, isConstrained, error — or
* reach for one of the convenience helpers below for the common
* cases.
*/
FakeBridge::macro('withNetworkStatus', function (array $status = []) {
// status() decodes with json_decode() (objects, not arrays), and
// an empty PHP array would JSON-encode to "[]" — a list, decoding
// back to an array and tripping status()'s ?object return type.
// Emit a literal "{}" for the empty case so it stays an object.
return $this->respondTo('Network.Status', $status === [] ? '{}' : $status);
});

/** Fake a connected Wi-Fi status (unmetered, unconstrained). */
FakeBridge::macro('withWifi', function (array $extra = []) {
return $this->withNetworkStatus(array_merge([
'connected' => true,
'type' => 'wifi',
'isExpensive' => false,
'isConstrained' => false,
], $extra));
});

/** Fake a connected cellular status (metered by default). */
FakeBridge::macro('withCellular', function (array $extra = []) {
return $this->withNetworkStatus(array_merge([
'connected' => true,
'type' => 'cellular',
'isExpensive' => true,
'isConstrained' => false,
], $extra));
});

/** Fake a disconnected status — no network at all. */
FakeBridge::macro('withOffline', function (array $extra = []) {
return $this->withNetworkStatus(array_merge([
'connected' => false,
'type' => 'unknown',
'isExpensive' => false,
'isConstrained' => false,
], $extra));
});

/**
* Fake the native error path (e.g. an Android catch reporting
* failure to read connectivity state).
*/
FakeBridge::macro('withError', function (string $error = 'Unknown error', array $extra = []) {
return $this->withNetworkStatus(array_merge([
'connected' => false,
'type' => 'error',
'isExpensive' => false,
'isConstrained' => false,
'error' => $error,
], $extra));
});

/** Assert the network status was checked (status()). */
FakeBridge::macro('assertNetworkChecked', function () {
return $this->assertCalled('Network.Status');
});
}
}
Loading