From 4fe77617b7e27401d4d8cf866d7116cb4120b08a Mon Sep 17 00:00:00 2001 From: activitysmith-bot Date: Mon, 1 Jun 2026 09:28:07 +0000 Subject: [PATCH 1/2] chore: regenerate SDK --- generated/Model/ContentStateEnd.php | 6 +++--- generated/Model/ContentStateStart.php | 6 +++--- generated/Model/ContentStateUpdate.php | 6 +++--- generated/Model/LiveActivityAction.php | 11 +---------- generated/Model/LiveActivityAlertBadge.php | 2 +- generated/Model/LiveActivityAlertIcon.php | 2 +- generated/Model/PushNotificationAction.php | 11 +---------- generated/Model/PushNotificationRequest.php | 10 +++++----- generated/Model/StreamContentState.php | 4 ++-- 9 files changed, 20 insertions(+), 38 deletions(-) diff --git a/generated/Model/ContentStateEnd.php b/generated/Model/ContentStateEnd.php index 2d24d1f..8b7d979 100644 --- a/generated/Model/ContentStateEnd.php +++ b/generated/Model/ContentStateEnd.php @@ -35,7 +35,7 @@ * ContentStateEnd Class Doc Comment * * @category Class - * @description End payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message, with optional icon and badge. Type is optional when ending an existing activity. You can send an updated number_of_steps here if the workflow changed after start. + * @description End payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, and segmented_progress. Type is optional when ending an existing activity. You can send an updated number_of_steps here if the workflow changed after start. * @package ActivitySmith\Generated * @author OpenAPI Generator team * @link https://openapi-generator.tech @@ -857,7 +857,7 @@ public function getIcon() /** * Sets icon * - * @param \ActivitySmith\Generated\Model\LiveActivityAlertIcon|null $icon Optional SF Symbol icon for type=alert. + * @param \ActivitySmith\Generated\Model\LiveActivityAlertIcon|null $icon Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats. * * @return self */ @@ -884,7 +884,7 @@ public function getBadge() /** * Sets badge * - * @param \ActivitySmith\Generated\Model\LiveActivityAlertBadge|null $badge Optional badge for type=alert. + * @param \ActivitySmith\Generated\Model\LiveActivityAlertBadge|null $badge Optional badge. Supported by alert, progress, and segmented_progress. * * @return self */ diff --git a/generated/Model/ContentStateStart.php b/generated/Model/ContentStateStart.php index 5f9839c..1a6f499 100644 --- a/generated/Model/ContentStateStart.php +++ b/generated/Model/ContentStateStart.php @@ -35,7 +35,7 @@ * ContentStateStart Class Doc Comment * * @category Class - * @description Start payload requires title and type. For segmented_progress include number_of_steps and current_step. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message, with optional icon and badge. For segmented_progress, number_of_steps is not locked and can be changed in later update or end calls. + * @description Start payload requires title and type. For segmented_progress include number_of_steps and current_step. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, and segmented_progress. For segmented_progress, number_of_steps is not locked and can be changed in later update or end calls. * @package ActivitySmith\Generated * @author OpenAPI Generator team * @link https://openapi-generator.tech @@ -849,7 +849,7 @@ public function getIcon() /** * Sets icon * - * @param \ActivitySmith\Generated\Model\LiveActivityAlertIcon|null $icon Optional SF Symbol icon for type=alert. + * @param \ActivitySmith\Generated\Model\LiveActivityAlertIcon|null $icon Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats. * * @return self */ @@ -876,7 +876,7 @@ public function getBadge() /** * Sets badge * - * @param \ActivitySmith\Generated\Model\LiveActivityAlertBadge|null $badge Optional badge for type=alert. + * @param \ActivitySmith\Generated\Model\LiveActivityAlertBadge|null $badge Optional badge. Supported by alert, progress, and segmented_progress. * * @return self */ diff --git a/generated/Model/ContentStateUpdate.php b/generated/Model/ContentStateUpdate.php index 428819c..33cb40f 100644 --- a/generated/Model/ContentStateUpdate.php +++ b/generated/Model/ContentStateUpdate.php @@ -35,7 +35,7 @@ * ContentStateUpdate Class Doc Comment * * @category Class - * @description Update payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message, with optional icon and badge. Type is optional when updating an existing activity. You can increase or decrease number_of_steps during updates. + * @description Update payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, and segmented_progress. Type is optional when updating an existing activity. You can increase or decrease number_of_steps during updates. * @package ActivitySmith\Generated * @author OpenAPI Generator team * @link https://openapi-generator.tech @@ -846,7 +846,7 @@ public function getIcon() /** * Sets icon * - * @param \ActivitySmith\Generated\Model\LiveActivityAlertIcon|null $icon Optional SF Symbol icon for type=alert. + * @param \ActivitySmith\Generated\Model\LiveActivityAlertIcon|null $icon Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats. * * @return self */ @@ -873,7 +873,7 @@ public function getBadge() /** * Sets badge * - * @param \ActivitySmith\Generated\Model\LiveActivityAlertBadge|null $badge Optional badge for type=alert. + * @param \ActivitySmith\Generated\Model\LiveActivityAlertBadge|null $badge Optional badge. Supported by alert, progress, and segmented_progress. * * @return self */ diff --git a/generated/Model/LiveActivityAction.php b/generated/Model/LiveActivityAction.php index f1030bc..eda35a0 100644 --- a/generated/Model/LiveActivityAction.php +++ b/generated/Model/LiveActivityAction.php @@ -312,10 +312,6 @@ public function listInvalidProperties() if ($this->container['url'] === null) { $invalidProperties[] = "'url' can't be null"; } - if (!preg_match("/^https:\/\//", $this->container['url'])) { - $invalidProperties[] = "invalid value for 'url', must be conform to the pattern /^https:\/\//."; - } - return $invalidProperties; } @@ -398,7 +394,7 @@ public function getUrl() /** * Sets url * - * @param string $url HTTPS URL. For open_url it is opened in browser. For webhook it is called by ActivitySmith backend. + * @param string $url Action URL. For open_url, use an HTTPS or shortcuts:// URL. For webhook, use an HTTPS URL called by the ActivitySmith backend. * * @return self */ @@ -407,11 +403,6 @@ public function setUrl($url) if (is_null($url)) { throw new \InvalidArgumentException('non-nullable url cannot be null'); } - - if ((!preg_match("/^https:\/\//", ObjectSerializer::toString($url)))) { - throw new \InvalidArgumentException("invalid value for \$url when calling LiveActivityAction., must conform to the pattern /^https:\/\//."); - } - $this->container['url'] = $url; return $this; diff --git a/generated/Model/LiveActivityAlertBadge.php b/generated/Model/LiveActivityAlertBadge.php index 39947fb..00515f3 100644 --- a/generated/Model/LiveActivityAlertBadge.php +++ b/generated/Model/LiveActivityAlertBadge.php @@ -35,7 +35,7 @@ * LiveActivityAlertBadge Class Doc Comment * * @category Class - * @description Optional badge for Alert Live Activities. + * @description Optional badge for Live Activities. * @package ActivitySmith\Generated * @author OpenAPI Generator team * @link https://openapi-generator.tech diff --git a/generated/Model/LiveActivityAlertIcon.php b/generated/Model/LiveActivityAlertIcon.php index 884ebe6..af5474c 100644 --- a/generated/Model/LiveActivityAlertIcon.php +++ b/generated/Model/LiveActivityAlertIcon.php @@ -35,7 +35,7 @@ * LiveActivityAlertIcon Class Doc Comment * * @category Class - * @description Optional SF Symbol icon for Alert Live Activities. + * @description Optional SF Symbol icon for Live Activities. * @package ActivitySmith\Generated * @author OpenAPI Generator team * @link https://openapi-generator.tech diff --git a/generated/Model/PushNotificationAction.php b/generated/Model/PushNotificationAction.php index 4888f80..27081a7 100644 --- a/generated/Model/PushNotificationAction.php +++ b/generated/Model/PushNotificationAction.php @@ -311,10 +311,6 @@ public function listInvalidProperties() if ($this->container['url'] === null) { $invalidProperties[] = "'url' can't be null"; } - if (!preg_match("/^https:\/\//", $this->container['url'])) { - $invalidProperties[] = "invalid value for 'url', must be conform to the pattern /^https:\/\//."; - } - return $invalidProperties; } @@ -397,7 +393,7 @@ public function getUrl() /** * Sets url * - * @param string $url HTTPS URL. For open_url it is opened in browser. For webhook it is called by ActivitySmith backend. + * @param string $url Action URL. For open_url, use an HTTPS or shortcuts:// URL. For webhook, use an HTTPS URL called by the ActivitySmith backend. * * @return self */ @@ -406,11 +402,6 @@ public function setUrl($url) if (is_null($url)) { throw new \InvalidArgumentException('non-nullable url cannot be null'); } - - if ((!preg_match("/^https:\/\//", ObjectSerializer::toString($url)))) { - throw new \InvalidArgumentException("invalid value for \$url when calling PushNotificationAction., must conform to the pattern /^https:\/\//."); - } - $this->container['url'] = $url; return $this; diff --git a/generated/Model/PushNotificationRequest.php b/generated/Model/PushNotificationRequest.php index 229a7a6..6b05aef 100644 --- a/generated/Model/PushNotificationRequest.php +++ b/generated/Model/PushNotificationRequest.php @@ -344,8 +344,8 @@ public function listInvalidProperties() $invalidProperties[] = "invalid value for 'media', must be conform to the pattern /^https:\/\//."; } - if (!is_null($this->container['redirection']) && !preg_match("/^https:\/\//", $this->container['redirection'])) { - $invalidProperties[] = "invalid value for 'redirection', must be conform to the pattern /^https:\/\//."; + if (!is_null($this->container['redirection']) && !preg_match("/^(https|shortcuts):\/\//", $this->container['redirection'])) { + $invalidProperties[] = "invalid value for 'redirection', must be conform to the pattern /^(https|shortcuts):\/\//."; } if (!is_null($this->container['actions']) && (count($this->container['actions']) > 4)) { @@ -493,7 +493,7 @@ public function getRedirection() /** * Sets redirection * - * @param string|null $redirection Optional HTTPS URL opened when user taps the notification body. Overrides the default tap target from `media` when both are provided. + * @param string|null $redirection Optional HTTPS or shortcuts:// URL opened when user taps the notification body. Overrides the default tap target from `media` when both are provided. * * @return self */ @@ -503,8 +503,8 @@ public function setRedirection($redirection) throw new \InvalidArgumentException('non-nullable redirection cannot be null'); } - if ((!preg_match("/^https:\/\//", ObjectSerializer::toString($redirection)))) { - throw new \InvalidArgumentException("invalid value for \$redirection when calling PushNotificationRequest., must conform to the pattern /^https:\/\//."); + if ((!preg_match("/^(https|shortcuts):\/\//", ObjectSerializer::toString($redirection)))) { + throw new \InvalidArgumentException("invalid value for \$redirection when calling PushNotificationRequest., must conform to the pattern /^(https|shortcuts):\/\//."); } $this->container['redirection'] = $redirection; diff --git a/generated/Model/StreamContentState.php b/generated/Model/StreamContentState.php index 748be3e..c1e837f 100644 --- a/generated/Model/StreamContentState.php +++ b/generated/Model/StreamContentState.php @@ -1015,7 +1015,7 @@ public function getIcon() /** * Sets icon * - * @param \ActivitySmith\Generated\Model\LiveActivityAlertIcon|null $icon Optional SF Symbol icon for type=alert. + * @param \ActivitySmith\Generated\Model\LiveActivityAlertIcon|null $icon Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats. * * @return self */ @@ -1042,7 +1042,7 @@ public function getBadge() /** * Sets badge * - * @param \ActivitySmith\Generated\Model\LiveActivityAlertBadge|null $badge Optional badge for type=alert. + * @param \ActivitySmith\Generated\Model\LiveActivityAlertBadge|null $badge Optional badge. Supported by alert, progress, and segmented_progress. * * @return self */ From f9983db5307d5428e8e108efa9e3bc0409f06d47 Mon Sep 17 00:00:00 2001 From: bardonadam Date: Mon, 1 Jun 2026 16:34:27 +0700 Subject: [PATCH 2/2] Add Live Activity icons and shortcut wrapper coverage --- README.md | 80 +++++++++-- generated/Model/LiveActivityAction.php | 17 ++- generated/Model/PushNotificationAction.php | 17 ++- src/LiveActivityColor.php | 1 + tests/ResourcesTest.php | 155 ++++++++++++++++++++- 5 files changed, 254 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 4512559..4d67586 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ See [API reference](https://activitysmith.com/docs/api-reference/introduction). - [Start & Update Live Activity](#start--update-live-activity) - [End Live Activity](#end-live-activity) - [Live Activity Action](#live-activity-action) + - [Icons and Badges](#icons-and-badges) + - [Live Activity Colors](#live-activity-colors) - [Channels](#channels) - [Widgets](#widgets) @@ -35,6 +37,7 @@ composer require activitysmith/activitysmith declare(strict_types=1); use ActivitySmith\ActivitySmith; +use ActivitySmith\LiveActivities; use ActivitySmith\LiveActivityAction; use ActivitySmith\LiveActivityAlertBadge; use ActivitySmith\LiveActivityAlertIcon; @@ -258,14 +261,6 @@ $activitysmith->liveActivities->stream( ); ``` -The `icon` symbol value is an Apple SF Symbol name. Browse the catalog with one of these tools: - -- [ActivitySmith app](https://apps.apple.com/us/app/activitysmith/id6752254835) - Open Settings -> SF Symbols to browse 45 hand-picked icons ready to use -- [SF Symbols](https://developer.apple.com/sf-symbols/) - Apple's official macOS app -- [Interactful](https://apps.apple.com/app/interactful/id1528095640) - free third-party iOS app listing all SF Symbols under Foundations -> Iconography - -`icon` and `badge` are optional. If you omit either one, that element is not shown in the Live Activity. - ### End Live Activity Call `endStream(...)` with the same `streamKey` to dismiss the Live Activity. You can include final values before it is removed. By default, iOS removes the Live Activity after two minutes. Set `autoDismissMinutes` to choose a different dismissal time, including `0` for immediate dismissal. @@ -346,6 +341,75 @@ $activitysmith->liveActivities->stream( ); ``` +### Icons and Badges + +Add more context to Live Activities with icons and badges. + +#### Icon + +Supported Live Activity types: `stats`, `metrics`, `progress`, `segmented_progress`, and `alert`. + +

+ Metrics Live Activity with an SF Symbol icon on the iPhone Lock Screen +

+ +```php +$activitysmith->liveActivities->stream( + 'prod-web-1', + contentState: LiveActivityContentState::make( + title: 'Server Health', + subtitle: 'prod-web-1', + type: LiveActivities::TYPE_METRICS, + icon: LiveActivityAlertIcon::make(symbol: 'server.rack', color: 'blue'), + metrics: [ + LiveActivityMetric::make(label: 'CPU', value: 18, unit: '%'), + LiveActivityMetric::make(label: 'MEM', value: 42, unit: '%'), + ], + ), +); +``` + +The `icon` symbol value is an Apple SF Symbol name. Browse the catalog with one of these tools: + +- [ActivitySmith app](https://apps.apple.com/us/app/activitysmith/id6752254835) - Open Settings -> SF Symbols to browse 45 hand-picked icons ready to use +- [SF Symbols](https://developer.apple.com/sf-symbols/) - Apple's official macOS app +- [Interactful](https://apps.apple.com/app/interactful/id1528095640) - free third-party iOS app listing all SF Symbols under Foundations -> Iconography + +#### Badge + +Badges are supported by `alert`, `progress`, and `segmented_progress` Live Activities. + +

+ Progress Live Activity with a badge on the iPhone Lock Screen +

+ +```php +$activitysmith->liveActivities->stream( + 'nightly-database-backup', + contentState: LiveActivityContentState::make( + title: 'Nightly Database Backup', + subtitle: 'verify restore', + type: LiveActivities::TYPE_PROGRESS, + badge: LiveActivityAlertBadge::make(title: 'S3', color: 'cyan'), + percentage: 62, + ), +); +``` + +### Live Activity Colors + +Choose from these colors for the Live Activity accent, including progress bars and action buttons, or apply them to an individual icon or badge: + +`lime`, `green`, `cyan`, `blue`, `purple`, `magenta`, `red`, `orange`, `yellow`, `gray` + ## Channels Channels are used to target specific team members or devices. Can be used for both push notifications and live activities. diff --git a/generated/Model/LiveActivityAction.php b/generated/Model/LiveActivityAction.php index eda35a0..88539d3 100644 --- a/generated/Model/LiveActivityAction.php +++ b/generated/Model/LiveActivityAction.php @@ -312,6 +312,15 @@ public function listInvalidProperties() if ($this->container['url'] === null) { $invalidProperties[] = "'url' can't be null"; } + if ($this->container['url'] !== null) { + $actionType = $this->container['type']; + if ($actionType === LiveActivityActionType::OPEN_URL && !preg_match('/^(https|shortcuts):\/\//', (string) $this->container['url'])) { + $invalidProperties[] = "invalid value for 'url', open_url must use https or shortcuts."; + } + if ($actionType === LiveActivityActionType::WEBHOOK && !preg_match('/^https:\/\//', (string) $this->container['url'])) { + $invalidProperties[] = "invalid value for 'url', webhook must use https."; + } + } return $invalidProperties; } @@ -403,6 +412,13 @@ public function setUrl($url) if (is_null($url)) { throw new \InvalidArgumentException('non-nullable url cannot be null'); } + $actionType = $this->container['type'] ?? null; + if ($actionType === LiveActivityActionType::OPEN_URL && !preg_match('/^(https|shortcuts):\/\//', (string) $url)) { + throw new \InvalidArgumentException("invalid value for \$url when calling LiveActivityAction., open_url must use https or shortcuts."); + } + if ($actionType === LiveActivityActionType::WEBHOOK && !preg_match('/^https:\/\//', (string) $url)) { + throw new \InvalidArgumentException("invalid value for \$url when calling LiveActivityAction., webhook must use https."); + } $this->container['url'] = $url; return $this; @@ -552,4 +568,3 @@ public function toHeaderValue() } } - diff --git a/generated/Model/PushNotificationAction.php b/generated/Model/PushNotificationAction.php index 27081a7..04e9cb9 100644 --- a/generated/Model/PushNotificationAction.php +++ b/generated/Model/PushNotificationAction.php @@ -311,6 +311,15 @@ public function listInvalidProperties() if ($this->container['url'] === null) { $invalidProperties[] = "'url' can't be null"; } + if ($this->container['url'] !== null) { + $actionType = $this->container['type']; + if ($actionType === PushNotificationActionType::OPEN_URL && !preg_match('/^(https|shortcuts):\/\//', (string) $this->container['url'])) { + $invalidProperties[] = "invalid value for 'url', open_url must use https or shortcuts."; + } + if ($actionType === PushNotificationActionType::WEBHOOK && !preg_match('/^https:\/\//', (string) $this->container['url'])) { + $invalidProperties[] = "invalid value for 'url', webhook must use https."; + } + } return $invalidProperties; } @@ -402,6 +411,13 @@ public function setUrl($url) if (is_null($url)) { throw new \InvalidArgumentException('non-nullable url cannot be null'); } + $actionType = $this->container['type'] ?? null; + if ($actionType === PushNotificationActionType::OPEN_URL && !preg_match('/^(https|shortcuts):\/\//', (string) $url)) { + throw new \InvalidArgumentException("invalid value for \$url when calling PushNotificationAction., open_url must use https or shortcuts."); + } + if ($actionType === PushNotificationActionType::WEBHOOK && !preg_match('/^https:\/\//', (string) $url)) { + throw new \InvalidArgumentException("invalid value for \$url when calling PushNotificationAction., webhook must use https."); + } $this->container['url'] = $url; return $this; @@ -551,4 +567,3 @@ public function toHeaderValue() } } - diff --git a/src/LiveActivityColor.php b/src/LiveActivityColor.php index 521cc08..7d8e040 100644 --- a/src/LiveActivityColor.php +++ b/src/LiveActivityColor.php @@ -15,4 +15,5 @@ final class LiveActivityColor public const RED = 'red'; public const ORANGE = 'orange'; public const YELLOW = 'yellow'; + public const GRAY = 'gray'; } diff --git a/tests/ResourcesTest.php b/tests/ResourcesTest.php index bb12200..0967f47 100644 --- a/tests/ResourcesTest.php +++ b/tests/ResourcesTest.php @@ -16,6 +16,11 @@ use ActivitySmith\Generated\Api\LiveActivitiesApi; use ActivitySmith\Generated\Api\MetricsApi; use ActivitySmith\Generated\Api\PushNotificationsApi; +use ActivitySmith\Generated\Model\LiveActivityAction as GeneratedLiveActivityAction; +use ActivitySmith\Generated\Model\LiveActivityActionType; +use ActivitySmith\Generated\Model\PushNotificationAction as GeneratedPushNotificationAction; +use ActivitySmith\Generated\Model\PushNotificationActionType; +use ActivitySmith\Generated\Model\PushNotificationRequest as GeneratedPushNotificationRequest; use PHPUnit\Framework\TestCase; final class ResourcesTest extends TestCase @@ -119,7 +124,7 @@ public function testPushActionHelper(): void PushAction::make( title: 'Open CRM Profile', type: 'open_url', - url: 'https://crm.example.com/customers/cus_9f3a1d' + url: 'shortcuts://run-shortcut?name=Open%20CRM' ), ], ) @@ -134,7 +139,7 @@ public function testPushActionHelper(): void [ 'title' => 'Open CRM Profile', 'type' => 'open_url', - 'url' => 'https://crm.example.com/customers/cus_9f3a1d', + 'url' => 'shortcuts://run-shortcut?name=Open%20CRM', ], ], ], @@ -145,6 +150,60 @@ public function testPushActionHelper(): void ); } + public function testGeneratedPushNotificationOpenUrlAllowsShortcuts(): void + { + $action = new GeneratedPushNotificationAction([ + 'title' => 'Chat', + 'type' => PushNotificationActionType::OPEN_URL, + 'url' => 'shortcuts://run-shortcut?name=JARVIS', + ]); + + $this->assertTrue($action->valid()); + } + + public function testGeneratedPushNotificationWebhookRejectsShortcuts(): void + { + $action = new GeneratedPushNotificationAction([ + 'title' => 'Chat', + 'type' => PushNotificationActionType::WEBHOOK, + 'url' => 'shortcuts://run-shortcut?name=JARVIS', + ]); + + $this->assertFalse($action->valid()); + } + + public function testGeneratedPushNotificationRedirectionAllowsShortcuts(): void + { + $request = new GeneratedPushNotificationRequest([ + 'title' => 'Task finished', + 'redirection' => 'shortcuts://run-shortcut?name=Jarvis', + ]); + + $this->assertTrue($request->valid()); + } + + public function testGeneratedLiveActivityOpenUrlAllowsShortcuts(): void + { + $action = new GeneratedLiveActivityAction([ + 'title' => 'Chat', + 'type' => LiveActivityActionType::OPEN_URL, + 'url' => 'shortcuts://run-shortcut?name=JARVIS', + ]); + + $this->assertTrue($action->valid()); + } + + public function testGeneratedLiveActivityWebhookRejectsShortcuts(): void + { + $action = new GeneratedLiveActivityAction([ + 'title' => 'Chat', + 'type' => LiveActivityActionType::WEBHOOK, + 'url' => 'shortcuts://run-shortcut?name=JARVIS', + ]); + + $this->assertFalse($action->valid()); + } + public function testNotificationsMapsChannelsToTarget(): void { $captured = []; @@ -185,7 +244,7 @@ public function testNotificationsPreserveMediaAndRedirection(): void ->onlyMethods(['sendPushNotification']) ->getMock(); - $api->expects($this->once()) + $api->expects($this->exactly(2)) ->method('sendPushNotification') ->willReturnCallback(function (...$args) use (&$captured, $response) { $captured[] = $args; @@ -200,9 +259,20 @@ public function testNotificationsPreserveMediaAndRedirection(): void ]; $this->assertSame($response, $resource->send($payload)); + $this->assertSame($response, $resource->send( + title: 'Run Shortcut', + redirection: 'shortcuts://run-shortcut?name=Jarvis' + )); $this->assertSame( [ [$payload, PushNotificationsApi::contentTypes['sendPushNotification'][0]], + [ + [ + 'title' => 'Run Shortcut', + 'redirection' => 'shortcuts://run-shortcut?name=Jarvis', + ], + PushNotificationsApi::contentTypes['sendPushNotification'][0], + ], ], $captured ); @@ -402,7 +472,7 @@ public function testLiveActivitiesPassActionPayloadsThrough(): void 'action' => [ 'title' => 'Open Workflow', 'type' => 'open_url', - 'url' => 'https://github.com/acme/payments-api/actions/runs/1234567890', + 'url' => 'shortcuts://run-shortcut?name=Deploy%20Status', ], ]; @@ -436,7 +506,7 @@ public function testLiveActivitiesPassActionPayloadsThrough(): void 'action' => [ 'title' => 'Open Workflow', 'type' => 'open_url', - 'url' => 'https://github.com/acme/payments-api/actions/runs/1234567890', + 'url' => 'shortcuts://run-shortcut?name=Deploy%20Status', ], ]; @@ -587,6 +657,79 @@ public function testLiveActivitiesSupportAlertHelpers(): void ); } + public function testLiveActivitiesSupportIconAndBadgeOnNonAlertTypes(): void + { + $response = (object) ['success' => true]; + $captured = []; + + $api = $this->getMockBuilder(LiveActivitiesApi::class) + ->disableOriginalConstructor() + ->onlyMethods(['reconcileLiveActivityStream']) + ->getMock(); + + $api->expects($this->exactly(2)) + ->method('reconcileLiveActivityStream') + ->willReturnCallback(function (...$args) use (&$captured, $response) { + $captured[] = $args; + return $response; + }); + + $resource = new LiveActivities($api); + + $resource->stream( + 'prod-web-1', + contentState: LiveActivityContentState::make( + title: 'Server Health', + subtitle: 'prod-web-1', + type: LiveActivities::TYPE_METRICS, + icon: LiveActivityAlertIcon::make(symbol: 'server.rack', color: 'blue'), + metrics: [LiveActivityMetric::make(label: 'CPU', value: 18, unit: '%')] + ) + ); + $resource->stream( + 'nightly-database-backup', + contentState: LiveActivityContentState::make( + title: 'Nightly Database Backup', + subtitle: 'verify restore', + type: LiveActivities::TYPE_PROGRESS, + badge: LiveActivityAlertBadge::make(title: 'S3', color: 'cyan'), + percentage: 62 + ) + ); + + $this->assertSame( + [ + [ + 'prod-web-1', + [ + 'content_state' => [ + 'title' => 'Server Health', + 'subtitle' => 'prod-web-1', + 'type' => LiveActivities::TYPE_METRICS, + 'icon' => ['symbol' => 'server.rack', 'color' => 'blue'], + 'metrics' => [['label' => 'CPU', 'value' => 18, 'unit' => '%']], + ], + ], + LiveActivitiesApi::contentTypes['reconcileLiveActivityStream'][0], + ], + [ + 'nightly-database-backup', + [ + 'content_state' => [ + 'title' => 'Nightly Database Backup', + 'subtitle' => 'verify restore', + 'type' => LiveActivities::TYPE_PROGRESS, + 'badge' => ['title' => 'S3', 'color' => 'cyan'], + 'percentage' => 62, + ], + ], + LiveActivitiesApi::contentTypes['reconcileLiveActivityStream'][0], + ], + ], + $captured + ); + } + public function testLiveActivitiesBuildRequestsFromNamedFields(): void { $response = (object) ['success' => true]; @@ -630,7 +773,7 @@ public function testLiveActivitiesBuildRequestsFromNamedFields(): void $action = LiveActivityAction::make( title: 'Open Dashboard', type: 'open_url', - url: 'https://ops.example.com/servers/prod-web-1' + url: 'shortcuts://run-shortcut?name=Open%20Dashboard' ); $state = LiveActivityContentState::make( title: 'Server Health',