feat(shorebird_code_push): track support (#232)
* feat(shorebird_code_push): track support * cleanup and add todos * docs * run ffigen * use c_char instead of char * Add channel support * tests * tests * update podfile.lock * Update example to include tracks selector --------- Co-authored-by: Bryan Oltman <bryan@shorebird.dev> Co-authored-by: Bryan Oltman <bryanoltman@gmail.com>
This commit is contained in:
@@ -13,4 +13,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
COCOAPODS: 1.16.1
|
||||
|
||||
@@ -29,6 +29,7 @@ class MyHomePage extends StatefulWidget {
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
final _updater = ShorebirdUpdater();
|
||||
late final bool _isUpdaterAvailable;
|
||||
var _currentTrack = UpdateTrack.stable;
|
||||
var _isCheckingForUpdates = false;
|
||||
Patch? _currentPatch;
|
||||
|
||||
@@ -54,10 +55,20 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
try {
|
||||
setState(() => _isCheckingForUpdates = true);
|
||||
// Check if there's an update available.
|
||||
final status = await _updater.checkForUpdate();
|
||||
final status = await _updater.checkForUpdate(track: _currentTrack);
|
||||
if (!mounted) return;
|
||||
// If there is an update available, show a banner.
|
||||
if (status == UpdateStatus.outdated) _showUpdateAvailableBanner();
|
||||
switch (status) {
|
||||
case UpdateStatus.upToDate:
|
||||
_showNoUpdateAvailableBanner();
|
||||
case UpdateStatus.outdated:
|
||||
_showUpdateAvailableBanner();
|
||||
case UpdateStatus.restartRequired:
|
||||
_showRestartBanner();
|
||||
case UpdateStatus.unavailable:
|
||||
// Do nothing, there is already a warning displayed at the top of the
|
||||
// screen.
|
||||
}
|
||||
} catch (error) {
|
||||
// If an error occurs, we log it for now.
|
||||
debugPrint('Error checking for update: $error');
|
||||
@@ -88,7 +99,9 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
..hideCurrentMaterialBanner()
|
||||
..showMaterialBanner(
|
||||
MaterialBanner(
|
||||
content: const Text('Update available'),
|
||||
content: Text(
|
||||
'Update available for the ${_currentTrack.name} track.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
@@ -104,6 +117,26 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showNoUpdateAvailableBanner() {
|
||||
ScaffoldMessenger.of(context)
|
||||
..hideCurrentMaterialBanner()
|
||||
..showMaterialBanner(
|
||||
MaterialBanner(
|
||||
content: Text(
|
||||
'No update available on the ${_currentTrack.name} track.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).hideCurrentMaterialBanner();
|
||||
},
|
||||
child: const Text('Dismiss'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showRestartBanner() {
|
||||
ScaffoldMessenger.of(context)
|
||||
..hideCurrentMaterialBanner()
|
||||
@@ -145,8 +178,10 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
Future<void> _downloadUpdate() async {
|
||||
_showDownloadingBanner();
|
||||
try {
|
||||
// Perform the update (e.g download the latest patch).
|
||||
await _updater.update();
|
||||
// Perform the update (e.g download the latest patch on [_currentTrack]).
|
||||
// Note that [track] is optional. Not passing it will default to the
|
||||
// stable track.
|
||||
await _updater.update(track: _currentTrack);
|
||||
if (!mounted) return;
|
||||
// Show a banner to inform the user that the update is ready and that they
|
||||
// need to restart the app.
|
||||
@@ -166,9 +201,21 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
backgroundColor: theme.colorScheme.inversePrimary,
|
||||
title: const Text('Shorebird Code Push'),
|
||||
),
|
||||
body: _isUpdaterAvailable
|
||||
? _CurrentPatchVersion(patch: _currentPatch)
|
||||
: const _ShorebirdUnavailable(),
|
||||
body: Column(
|
||||
children: [
|
||||
if (!_isUpdaterAvailable) const _ShorebirdUnavailable(),
|
||||
const Spacer(),
|
||||
_CurrentPatchVersion(patch: _currentPatch),
|
||||
const SizedBox(height: 12),
|
||||
_TrackPicker(
|
||||
currentTrack: _currentTrack,
|
||||
onChanged: (track) {
|
||||
setState(() => _currentTrack = track);
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _isCheckingForUpdates ? null : _checkForUpdate,
|
||||
tooltip: 'Check for update',
|
||||
@@ -224,6 +271,47 @@ class _CurrentPatchVersion extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget that allows selection of update track.
|
||||
class _TrackPicker extends StatelessWidget {
|
||||
const _TrackPicker({
|
||||
required this.currentTrack,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
final UpdateTrack currentTrack;
|
||||
|
||||
final ValueChanged<UpdateTrack> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const Text('Update track:'),
|
||||
SegmentedButton<UpdateTrack>(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
label: Text('Stable'),
|
||||
value: UpdateTrack.stable,
|
||||
),
|
||||
ButtonSegment(
|
||||
label: Text('Beta'),
|
||||
icon: Icon(Icons.science),
|
||||
value: UpdateTrack.beta,
|
||||
),
|
||||
ButtonSegment(
|
||||
label: Text('Staging'),
|
||||
icon: Icon(Icons.construction),
|
||||
value: UpdateTrack.staging,
|
||||
),
|
||||
],
|
||||
selected: {currentTrack},
|
||||
onSelectionChanged: (tracks) => onChanged(tracks.single),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A reusable loading indicator.
|
||||
class _LoadingIndicator extends StatelessWidget {
|
||||
const _LoadingIndicator();
|
||||
|
||||
@@ -8,4 +8,5 @@ export 'src/shorebird_updater.dart'
|
||||
ShorebirdUpdater,
|
||||
UpdateException,
|
||||
UpdateFailureReason,
|
||||
UpdateStatus;
|
||||
UpdateStatus,
|
||||
UpdateTrack;
|
||||
|
||||
@@ -189,13 +189,13 @@ class UpdaterBindings {
|
||||
_waitpidPtr.asFunction<int Function(int, ffi.Pointer<ffi.Int>, int)>();
|
||||
|
||||
int waitid(
|
||||
idtype_t arg0,
|
||||
Dart__uint32_t arg1,
|
||||
int arg0,
|
||||
int arg1,
|
||||
ffi.Pointer<siginfo_t> arg2,
|
||||
int arg3,
|
||||
) {
|
||||
return _waitid(
|
||||
arg0.value,
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
arg3,
|
||||
@@ -204,8 +204,8 @@ class UpdaterBindings {
|
||||
|
||||
late final _waitidPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Int Function(ffi.UnsignedInt, id_t, ffi.Pointer<siginfo_t>,
|
||||
ffi.Int)>>('waitid');
|
||||
ffi.Int Function(
|
||||
ffi.Int32, id_t, ffi.Pointer<siginfo_t>, ffi.Int)>>('waitid');
|
||||
late final _waitid = _waitidPtr
|
||||
.asFunction<int Function(int, int, ffi.Pointer<siginfo_t>, int)>();
|
||||
|
||||
@@ -2456,16 +2456,26 @@ class UpdaterBindings {
|
||||
late final _shorebird_free_update_result = _shorebird_free_update_resultPtr
|
||||
.asFunction<void Function(ffi.Pointer<UpdateResult>)>();
|
||||
|
||||
/// Check for an update. Returns true if an update is available.
|
||||
bool shorebird_check_for_update() {
|
||||
return _shorebird_check_for_update();
|
||||
/// Check for an update on the first non-null channel of:
|
||||
/// 1. `c_channel`
|
||||
/// 2. The channel specified in shorebird.yaml
|
||||
/// 3. The default "stable" channel
|
||||
///
|
||||
/// Returns true if an update exists that has not yet been downloaded.
|
||||
bool shorebird_check_for_downloadable_update(
|
||||
ffi.Pointer<ffi.Char> c_channel,
|
||||
) {
|
||||
return _shorebird_check_for_downloadable_update(
|
||||
c_channel,
|
||||
);
|
||||
}
|
||||
|
||||
late final _shorebird_check_for_updatePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Bool Function()>>(
|
||||
'shorebird_check_for_update');
|
||||
late final _shorebird_check_for_update =
|
||||
_shorebird_check_for_updatePtr.asFunction<bool Function()>();
|
||||
late final _shorebird_check_for_downloadable_updatePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Bool Function(ffi.Pointer<ffi.Char>)>>(
|
||||
'shorebird_check_for_downloadable_update');
|
||||
late final _shorebird_check_for_downloadable_update =
|
||||
_shorebird_check_for_downloadable_updatePtr
|
||||
.asFunction<bool Function(ffi.Pointer<ffi.Char>)>();
|
||||
|
||||
/// Synchronously download an update if one is available.
|
||||
void shorebird_update() {
|
||||
@@ -2477,17 +2487,26 @@ class UpdaterBindings {
|
||||
late final _shorebird_update =
|
||||
_shorebird_updatePtr.asFunction<void Function()>();
|
||||
|
||||
/// Synchronously download an update if one is available.
|
||||
/// Synchronously download an update on the first non-null channel of:
|
||||
/// 1. `c_channel`
|
||||
/// 2. The channel specified in shorebird.yaml
|
||||
/// 3. The default "stable" channel
|
||||
///
|
||||
/// Returns an [UpdateResult] indicating whether the update was successful.
|
||||
ffi.Pointer<UpdateResult> shorebird_update_with_result() {
|
||||
return _shorebird_update_with_result();
|
||||
ffi.Pointer<UpdateResult> shorebird_update_with_result(
|
||||
ffi.Pointer<ffi.Char> c_channel,
|
||||
) {
|
||||
return _shorebird_update_with_result(
|
||||
c_channel,
|
||||
);
|
||||
}
|
||||
|
||||
late final _shorebird_update_with_resultPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<UpdateResult> Function()>>(
|
||||
'shorebird_update_with_result');
|
||||
late final _shorebird_update_with_resultPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<UpdateResult> Function(
|
||||
ffi.Pointer<ffi.Char>)>>('shorebird_update_with_result');
|
||||
late final _shorebird_update_with_result = _shorebird_update_with_resultPtr
|
||||
.asFunction<ffi.Pointer<UpdateResult> Function()>();
|
||||
.asFunction<ffi.Pointer<UpdateResult> Function(ffi.Pointer<ffi.Char>)>();
|
||||
|
||||
/// Start a thread to download an update if one is available.
|
||||
void shorebird_start_update_thread() {
|
||||
@@ -2640,20 +2659,10 @@ final class _opaque_pthread_t extends ffi.Struct {
|
||||
external ffi.Array<ffi.Char> __opaque;
|
||||
}
|
||||
|
||||
enum idtype_t {
|
||||
P_ALL(0),
|
||||
P_PID(1),
|
||||
P_PGID(2);
|
||||
|
||||
final int value;
|
||||
const idtype_t(this.value);
|
||||
|
||||
static idtype_t fromValue(int value) => switch (value) {
|
||||
0 => P_ALL,
|
||||
1 => P_PID,
|
||||
2 => P_PGID,
|
||||
_ => throw ArgumentError("Unknown value for idtype_t: $value"),
|
||||
};
|
||||
abstract class idtype_t {
|
||||
static const int P_ALL = 0;
|
||||
static const int P_PID = 1;
|
||||
static const int P_PGID = 2;
|
||||
}
|
||||
|
||||
final class __darwin_arm_exception_state extends ffi.Struct {
|
||||
@@ -4060,6 +4069,8 @@ const int __MAC_14_5 = 140500;
|
||||
|
||||
const int __MAC_15_0 = 150000;
|
||||
|
||||
const int __MAC_15_1 = 150100;
|
||||
|
||||
const int __IPHONE_2_0 = 20000;
|
||||
|
||||
const int __IPHONE_2_1 = 20100;
|
||||
@@ -4220,6 +4231,8 @@ const int __IPHONE_17_5 = 170500;
|
||||
|
||||
const int __IPHONE_18_0 = 180000;
|
||||
|
||||
const int __IPHONE_18_1 = 180100;
|
||||
|
||||
const int __WATCHOS_1_0 = 10000;
|
||||
|
||||
const int __WATCHOS_2_0 = 20000;
|
||||
@@ -4316,6 +4329,8 @@ const int __WATCHOS_10_5 = 100500;
|
||||
|
||||
const int __WATCHOS_11_0 = 110000;
|
||||
|
||||
const int __WATCHOS_11_1 = 110100;
|
||||
|
||||
const int __TVOS_9_0 = 90000;
|
||||
|
||||
const int __TVOS_9_1 = 90100;
|
||||
@@ -4414,6 +4429,8 @@ const int __TVOS_17_5 = 170500;
|
||||
|
||||
const int __TVOS_18_0 = 180000;
|
||||
|
||||
const int __TVOS_18_1 = 180100;
|
||||
|
||||
const int __BRIDGEOS_2_0 = 20000;
|
||||
|
||||
const int __BRIDGEOS_3_0 = 30000;
|
||||
@@ -4468,6 +4485,8 @@ const int __BRIDGEOS_8_5 = 80500;
|
||||
|
||||
const int __BRIDGEOS_9_0 = 90000;
|
||||
|
||||
const int __BRIDGEOS_9_1 = 90100;
|
||||
|
||||
const int __DRIVERKIT_19_0 = 190000;
|
||||
|
||||
const int __DRIVERKIT_20_0 = 200000;
|
||||
@@ -4496,6 +4515,8 @@ const int __DRIVERKIT_23_5 = 230500;
|
||||
|
||||
const int __DRIVERKIT_24_0 = 240000;
|
||||
|
||||
const int __DRIVERKIT_24_1 = 240100;
|
||||
|
||||
const int __VISIONOS_1_0 = 10000;
|
||||
|
||||
const int __VISIONOS_1_1 = 10100;
|
||||
@@ -4504,6 +4525,8 @@ const int __VISIONOS_1_2 = 10200;
|
||||
|
||||
const int __VISIONOS_2_0 = 20000;
|
||||
|
||||
const int __VISIONOS_2_1 = 20100;
|
||||
|
||||
const int MAC_OS_X_VERSION_10_0 = 1000;
|
||||
|
||||
const int MAC_OS_X_VERSION_10_1 = 1010;
|
||||
@@ -4628,9 +4651,11 @@ const int MAC_OS_VERSION_14_5 = 140500;
|
||||
|
||||
const int MAC_OS_VERSION_15_0 = 150000;
|
||||
|
||||
const int __MAC_OS_X_VERSION_MIN_REQUIRED = 140000;
|
||||
const int MAC_OS_VERSION_15_1 = 150100;
|
||||
|
||||
const int __MAC_OS_X_VERSION_MAX_ALLOWED = 150000;
|
||||
const int __MAC_OS_X_VERSION_MIN_REQUIRED = 150000;
|
||||
|
||||
const int __MAC_OS_X_VERSION_MAX_ALLOWED = 150100;
|
||||
|
||||
const int __ENABLE_LEGACY_MAC_AVAILABILITY = 1;
|
||||
|
||||
|
||||
@@ -121,8 +121,7 @@ abstract class ShorebirdUpdater {
|
||||
/// Checks for available updates and returns the [UpdateStatus].
|
||||
/// This method should be used to determine the update status before calling
|
||||
/// [update].
|
||||
/// Returns `null` if the updater is not available.
|
||||
Future<UpdateStatus?> checkForUpdate();
|
||||
Future<UpdateStatus> checkForUpdate({UpdateTrack? track});
|
||||
|
||||
/// Updates the app to the latest patch (if available).
|
||||
/// Future will complete once the update is fully downloaded and ready
|
||||
@@ -136,5 +135,17 @@ abstract class ShorebirdUpdater {
|
||||
/// * [isAvailable], which indicates whether the updater is available.
|
||||
/// * [checkForUpdate], which should be called to check if an update is
|
||||
/// available before calling this method.
|
||||
Future<void> update();
|
||||
Future<void> update({UpdateTrack? track});
|
||||
}
|
||||
|
||||
/// The track to check for updates on.
|
||||
enum UpdateTrack {
|
||||
/// The staging track used for internal testing.
|
||||
staging,
|
||||
|
||||
/// The beta track used for public testing.
|
||||
beta,
|
||||
|
||||
/// The stable track used for general availability.
|
||||
stable,
|
||||
}
|
||||
|
||||
@@ -66,12 +66,16 @@ class ShorebirdUpdaterImpl implements ShorebirdUpdater {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UpdateStatus> checkForUpdate() async {
|
||||
Future<UpdateStatus> checkForUpdate({UpdateTrack? track}) async {
|
||||
if (!_isAvailable) return UpdateStatus.unavailable;
|
||||
|
||||
final isUpdateAvailable = await _run(_updater.checkForUpdate);
|
||||
// First, check to see whether an update is available for download.
|
||||
final isUpdateAvailable =
|
||||
await _run(() => _updater.checkForDownloadableUpdate(track: track));
|
||||
if (isUpdateAvailable) return UpdateStatus.outdated;
|
||||
|
||||
// If no new update is available for download, see if a new patch exists
|
||||
// on disk that requires a restart.
|
||||
final (current, next) = await (readCurrentPatch(), readNextPatch()).wait;
|
||||
return next != null && current?.number != next.number
|
||||
? UpdateStatus.restartRequired
|
||||
@@ -79,13 +83,13 @@ class ShorebirdUpdaterImpl implements ShorebirdUpdater {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> update() async {
|
||||
Future<void> update({UpdateTrack? track}) async {
|
||||
if (!_isAvailable) return;
|
||||
|
||||
Pointer<UpdateResult> result = nullptr;
|
||||
|
||||
try {
|
||||
result = await _run(_updater.update);
|
||||
result = await _run(() => _updater.update(track: track));
|
||||
} catch (_) {
|
||||
return _legacyFallback();
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ class ShorebirdUpdaterImpl implements ShorebirdUpdater {
|
||||
Future<Patch?> readNextPatch() async => null;
|
||||
|
||||
@override
|
||||
Future<UpdateStatus> checkForUpdate() async => UpdateStatus.unavailable;
|
||||
Future<UpdateStatus> checkForUpdate({UpdateTrack? track}) async =>
|
||||
UpdateStatus.unavailable;
|
||||
|
||||
@override
|
||||
Future<void> update() async {}
|
||||
Future<void> update({UpdateTrack? track}) async {}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'dart:ffi' as ffi;
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:shorebird_code_push/src/generated/updater_bindings.g.dart';
|
||||
import 'package:shorebird_code_push/src/shorebird_updater.dart';
|
||||
|
||||
/// {@template updater}
|
||||
/// A wrapper around the generated [UpdaterBindings] that, when necessary,
|
||||
@@ -20,9 +22,6 @@ class Updater {
|
||||
/// The currently active patch number.
|
||||
int currentPatchNumber() => bindings.shorebird_current_boot_patch_number();
|
||||
|
||||
/// Whether a new patch is available.
|
||||
bool checkForUpdate() => bindings.shorebird_check_for_update();
|
||||
|
||||
/// The next patch number that will be loaded. Will be the same as
|
||||
/// currentPatchNumber if no new patch is available.
|
||||
int nextPatchNumber() => bindings.shorebird_next_boot_patch_number();
|
||||
@@ -30,9 +29,20 @@ class Updater {
|
||||
/// Downloads the latest patch, if available.
|
||||
void downloadUpdate() => bindings.shorebird_update();
|
||||
|
||||
// New Methods added to support v2.0.0 of the Dart APIs //
|
||||
|
||||
/// Whether a new patch is available for download.
|
||||
bool checkForDownloadableUpdate({UpdateTrack? track}) =>
|
||||
bindings.shorebird_check_for_downloadable_update(
|
||||
track == null ? ffi.nullptr : track.name.toNativeUtf8().cast<Char>(),
|
||||
);
|
||||
|
||||
/// Downloads the latest patch, if available and returns an [UpdateResult]
|
||||
/// to indicate whether the update was successful.
|
||||
Pointer<UpdateResult> update() => bindings.shorebird_update_with_result();
|
||||
Pointer<UpdateResult> update({UpdateTrack? track}) =>
|
||||
bindings.shorebird_update_with_result(
|
||||
track == null ? ffi.nullptr : track.name.toNativeUtf8().cast<Char>(),
|
||||
);
|
||||
|
||||
/// Frees an update result allocated by the updater.
|
||||
void freeUpdateResult(Pointer<UpdateResult> ptr) =>
|
||||
|
||||
@@ -242,7 +242,7 @@ void main() {
|
||||
group('when updater has an update available', () {
|
||||
setUp(() {
|
||||
when(updater.currentPatchNumber).thenReturn(0);
|
||||
when(updater.checkForUpdate).thenReturn(true);
|
||||
when(updater.checkForDownloadableUpdate).thenReturn(true);
|
||||
shorebirdUpdater = ShorebirdUpdaterImpl(updater, run: run);
|
||||
});
|
||||
|
||||
@@ -258,7 +258,7 @@ void main() {
|
||||
setUp(() {
|
||||
when(updater.currentPatchNumber).thenReturn(0);
|
||||
when(updater.nextPatchNumber).thenReturn(1);
|
||||
when(updater.checkForUpdate).thenReturn(false);
|
||||
when(updater.checkForDownloadableUpdate).thenReturn(false);
|
||||
shorebirdUpdater = ShorebirdUpdaterImpl(updater, run: run);
|
||||
});
|
||||
|
||||
@@ -274,7 +274,7 @@ void main() {
|
||||
setUp(() {
|
||||
when(updater.currentPatchNumber).thenReturn(1);
|
||||
when(updater.nextPatchNumber).thenReturn(1);
|
||||
when(updater.checkForUpdate).thenReturn(false);
|
||||
when(updater.checkForDownloadableUpdate).thenReturn(false);
|
||||
shorebirdUpdater = ShorebirdUpdaterImpl(updater, run: run);
|
||||
});
|
||||
|
||||
@@ -285,6 +285,28 @@ void main() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when a track is provided', () {
|
||||
const track = UpdateTrack.beta;
|
||||
|
||||
setUp(() {
|
||||
when(updater.currentPatchNumber).thenReturn(0);
|
||||
when(
|
||||
() => updater.checkForDownloadableUpdate(track: track),
|
||||
).thenReturn(true);
|
||||
shorebirdUpdater = ShorebirdUpdaterImpl(updater, run: run);
|
||||
});
|
||||
|
||||
test('forwards the provided track to the underlying updater call',
|
||||
() async {
|
||||
await expectLater(
|
||||
shorebirdUpdater.checkForUpdate(track: track),
|
||||
completion(equals(UpdateStatus.outdated)),
|
||||
);
|
||||
verify(() => updater.checkForDownloadableUpdate(track: track))
|
||||
.called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('update', () {
|
||||
@@ -565,6 +587,29 @@ Please upgrade the Shorebird Engine for improved error messages.''',
|
||||
verify(() => updater.freeUpdateResult(any())).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when a track is provided', () {
|
||||
const track = UpdateTrack.beta;
|
||||
|
||||
setUp(() {
|
||||
when(updater.currentPatchNumber).thenReturn(0);
|
||||
final result = calloc.allocate<UpdateResult>(sizeOf<UpdateResult>());
|
||||
result.ref.status = SHOREBIRD_UPDATE_INSTALLED;
|
||||
addTearDown(() => calloc.free(result));
|
||||
when(() => updater.update(track: track)).thenReturn(result);
|
||||
shorebirdUpdater = ShorebirdUpdaterImpl(updater, run: run);
|
||||
});
|
||||
|
||||
test('forwards the provided track to the underlying updater call',
|
||||
() async {
|
||||
await expectLater(
|
||||
shorebirdUpdater.update(track: track),
|
||||
completes,
|
||||
);
|
||||
verify(() => updater.update(track: track)).called(1);
|
||||
verify(() => updater.freeUpdateResult(any())).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shorebird_code_push/shorebird_code_push.dart';
|
||||
import 'package:shorebird_code_push/src/generated/updater_bindings.g.dart';
|
||||
import 'package:shorebird_code_push/src/updater.dart';
|
||||
import 'package:test/test.dart';
|
||||
@@ -38,17 +39,55 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('checkForUpdate', () {
|
||||
group('checkForDownloadableUpdate', () {
|
||||
test('forwards the result of shorebird_check_for_update', () {
|
||||
when(
|
||||
() => updaterBindings.shorebird_check_for_update(),
|
||||
() => updaterBindings.shorebird_check_for_downloadable_update(
|
||||
nullptr,
|
||||
),
|
||||
).thenReturn(true);
|
||||
expect(updater.checkForUpdate(), isTrue);
|
||||
expect(updater.checkForDownloadableUpdate(), isTrue);
|
||||
|
||||
when(
|
||||
() => updaterBindings.shorebird_check_for_update(),
|
||||
() => updaterBindings.shorebird_check_for_downloadable_update(
|
||||
nullptr,
|
||||
),
|
||||
).thenReturn(false);
|
||||
expect(updater.checkForUpdate(), isFalse);
|
||||
expect(updater.checkForDownloadableUpdate(), isFalse);
|
||||
});
|
||||
|
||||
group('when a track is provided', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => updaterBindings.shorebird_check_for_downloadable_update(
|
||||
any(),
|
||||
),
|
||||
).thenReturn(true);
|
||||
});
|
||||
|
||||
test('forwards the result of shorebird_check_for_update', () {
|
||||
expect(
|
||||
updater.checkForDownloadableUpdate(track: UpdateTrack.beta),
|
||||
isTrue,
|
||||
);
|
||||
|
||||
expect(
|
||||
updater.checkForDownloadableUpdate(track: UpdateTrack.stable),
|
||||
isTrue,
|
||||
);
|
||||
|
||||
final captured = verify(
|
||||
() => updaterBindings.shorebird_check_for_downloadable_update(
|
||||
captureAny(),
|
||||
),
|
||||
).captured;
|
||||
expect(
|
||||
captured.map(
|
||||
(cstr) => (cstr as Pointer<Char>).cast<Utf8>().toDartString(),
|
||||
),
|
||||
equals(['beta', 'stable']),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,10 +112,12 @@ void main() {
|
||||
group('update', () {
|
||||
test('calls bindings.shorebird_update_with_result', () {
|
||||
when(
|
||||
() => updaterBindings.shorebird_update_with_result(),
|
||||
() => updaterBindings.shorebird_update_with_result(nullptr),
|
||||
).thenReturn(nullptr);
|
||||
updater.update();
|
||||
verify(() => updaterBindings.shorebird_update_with_result()).called(1);
|
||||
verify(
|
||||
() => updaterBindings.shorebird_update_with_result(nullptr),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user