diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 3d181c8..9f2ce9a 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -16,6 +16,7 @@ jobs:
uses: phpbb-extensions/test-framework/.github/workflows/tests.yml@3.3.x
with:
EXTNAME: phpbb/webpushnotifications # Your extension vendor/package name
+ EXECUTABLE_FILES: 0
RUN_NPM_INSTALL: 1
RUN_COMPOSER_INSTALL: 1
PHP_VERSION_MATRIX: '["7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"]'
diff --git a/README.md b/README.md
index b74798a..d2cb49a 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
This is the repository for the development of the phpBB Browser Push Notifications extension.
[](https://github.com/phpbb-extensions/webpushnotifications/actions)
+[](https://codecov.io/gh/phpbb-extensions/webpushnotifications)
An official phpBB extension that allows board users to receive browser-based push notifications.
diff --git a/language/en/webpushnotifications_faq.php b/language/en/webpushnotifications_faq.php
index efe4855..3eb3aed 100644
--- a/language/en/webpushnotifications_faq.php
+++ b/language/en/webpushnotifications_faq.php
@@ -42,11 +42,11 @@
'HELP_FAQ_WPN_WHAT_QUESTION' => 'What are web push notifications?',
'HELP_FAQ_WPN_WHAT_ANSWER' => 'Web push notifications enhance phpBB’s notification system by allowing real-time notifications to be sent directly to your desktop or mobile device, even if you’re not actively browsing the forum. These notifications function like app alerts, providing instant updates for private messages, post interactions, moderation actions, and more.',
'HELP_FAQ_WPN_HOW_QUESTION' => 'How can I receive forum notification alerts on my computer or mobile device?',
- 'HELP_FAQ_WPN_HOW_ANSWER' => 'Navigate to “Notification options” in your UCP (User Control Panel) and click “Enable web push notifications.” Your browser may ask for permission to send notifications—be sure to allow it. If you’re still not receiving notifications, check your device’s system settings to ensure notifications are enabled for your browser. For mobile devices such as iPhone or iPad, you may need to add the forum site to your Home Screen for push notifications to work, effectively turning it into a standalone web app. Follow your mobile device’s instructions to enable push notifications for iPhone/iPad or Android.',
+ 'HELP_FAQ_WPN_HOW_ANSWER' => 'Navigate to “Notification options” in your UCP (User Control Panel) and click “Enable push notifications.” Your browser may ask for permission to send notifications—be sure to allow it. If you’re still not receiving notifications, check your device’s system settings to ensure notifications are enabled for your browser. For mobile devices such as iPhone or iPad, you may need to add the forum site to your Home Screen for push notifications to work, effectively turning it into a standalone web app. Follow your mobile device’s instructions to enable push notifications for iPhone/iPad or Android.',
'HELP_FAQ_WPN_SESSION_QUESTION' => 'Will I receive notifications if I am logged out?',
'HELP_FAQ_WPN_SESSION_ANSWER' => 'Yes, you will continue to receive notifications even if you’re logged out.',
- 'HELP_FAQ_WPN_SUBBING_QUESTION' => 'Why are the “Enable Push Notifications” buttons disabled?',
- 'HELP_FAQ_WPN_SUBBING_ANSWER' => 'If the “Enable Push Notifications” buttons is visible but cannot be clicked, your browser or device likely doesn’t support push notifications. Try using a different browser or device that supports this feature.',
+ 'HELP_FAQ_WPN_SUBBING_QUESTION' => 'Why are the “Enable push notifications” buttons disabled?',
+ 'HELP_FAQ_WPN_SUBBING_ANSWER' => 'If the “Enable push notifications” buttons is visible but cannot be clicked, your browser or device likely doesn’t support push notifications. Try using a different browser or device that supports this feature.',
'HELP_FAQ_WPN_GENERAL_QUESTION' => 'What if I’m still having trouble receiving notifications?',
'HELP_FAQ_WPN_GENERAL_ANSWER' => 'Make sure this forum is allowed to send notifications in your browser settings. Also, verify that your device’s system settings permit notifications from your web browser or app. Some browsers deliver notifications even when closed, whilst others only do so when the browser is open. View this table for browser support information. Finally, if you’re using an ad blocker, review its settings to make sure it’s not configured to block push notifications.',
]);
diff --git a/tests/acp/acp_module_test.php b/tests/acp/acp_module_test.php
index 9771866..83c5391 100644
--- a/tests/acp/acp_module_test.php
+++ b/tests/acp/acp_module_test.php
@@ -301,9 +301,31 @@ public function test_main_rejects_invalid_form_key($mode): void
->with('submit')
->willReturn(true);
- $this->setExpectedTriggerError(E_USER_WARNING, 'FORM_INVALID');
+ $this->assertTriggerError(E_USER_WARNING, 'FORM_INVALID', function() use ($mode) {
+ $this->create_module('adm.php?i=test&mode=' . $mode)->main('', $mode);
+ });
+ }
+
+ public function valid_submit_mode_data(): array
+ {
+ return [
+ 'webpush' => ['webpush', 'save_settings'],
+ 'pwa' => ['pwa', 'save_pwa_settings'],
+ ];
+ }
+
+ /**
+ * @dataProvider valid_submit_mode_data
+ */
+ public function test_main_routes_valid_submission($mode, $expected_method): void
+ {
+ $this->request->method('is_set_post')->with('submit')->willReturn(true);
+ $module = new \phpbb\webpushnotifications\acp\routing_acp_module();
+ $module->u_action = 'adm.php?i=test&mode=' . $mode;
+
+ $module->main('', $mode);
- $this->create_module('adm.php?i=test&mode=' . $mode)->main('', $mode);
+ self::assertSame([$expected_method], $module->calls);
}
public function webpush_save_data(): array
@@ -402,10 +424,14 @@ public function test_save_settings_validates_and_persists_webpush_config(array $
if ($expect_saved)
{
- $this->setExpectedTriggerError(E_USER_NOTICE, 'CONFIG_UPDATED');
+ $this->assertTriggerError(E_USER_NOTICE, 'CONFIG_UPDATED', function() {
+ $this->create_module()->save_settings();
+ });
+ }
+ else
+ {
+ $this->create_module()->save_settings();
}
-
- $this->create_module()->save_settings();
foreach ($expected_config as $name => $value)
{
@@ -477,8 +503,8 @@ public function pwa_save_data(): array
],
[
[
- 'pwa_bg_color' => '#fff000',
- 'pwa_theme_color' => '#000fff',
+ 'pwa_bg_color' => '',
+ 'pwa_theme_color' => '',
],
[
'pwa_bg_color' => '',
@@ -643,10 +669,14 @@ public function test_save_pwa_settings_validates_and_persists_pwa_config(array $
if ($expect_saved)
{
- $this->setExpectedTriggerError(E_USER_NOTICE, 'CONFIG_UPDATED');
+ $this->assertTriggerError(E_USER_NOTICE, 'CONFIG_UPDATED', function() {
+ $this->create_module('adm.php?i=test&mode=pwa')->save_pwa_settings();
+ });
+ }
+ else
+ {
+ $this->create_module('adm.php?i=test&mode=pwa')->save_pwa_settings();
}
-
- $this->create_module('adm.php?i=test&mode=pwa')->save_pwa_settings();
foreach ($expected_config as $name => $value)
{
@@ -738,6 +768,31 @@ protected function set_protected_property($object, $property, $value): void
$property->setValue($object, $value);
}
+ protected function assertTriggerError($errno, $message, callable $callback): void
+ {
+ $caught = null;
+ set_error_handler(static function($severity, $error_message, $file, $line) {
+ throw new \ErrorException($error_message, 0, $severity, $file, $line);
+ }, $errno);
+
+ try
+ {
+ $callback();
+ }
+ catch (\ErrorException $exception)
+ {
+ $caught = $exception;
+ }
+ finally
+ {
+ restore_error_handler();
+ }
+
+ self::assertNotNull($caught, 'Expected trigger_error() was not raised.');
+ self::assertSame($errno, $caught->getSeverity());
+ self::assertStringContainsString($message, $caught->getMessage());
+ }
+
protected function get_protected_property($object, $property)
{
$reflection = new \ReflectionClass($object);
@@ -826,3 +881,26 @@ function check_form_key()
{
return \phpbb\webpushnotifications\tests\acp\acp_module_test::$valid_form;
}
+
+class routing_acp_module extends wpn_acp_module
+{
+ public $calls = [];
+
+ public function save_settings()
+ {
+ $this->calls[] = __FUNCTION__;
+ }
+
+ public function save_pwa_settings()
+ {
+ $this->calls[] = __FUNCTION__;
+ }
+
+ public function display_settings()
+ {
+ }
+
+ public function display_pwa_settings()
+ {
+ }
+}
diff --git a/tests/controller/controller_webpush_test.php b/tests/controller/controller_webpush_test.php
index 8ea7183..2283a23 100644
--- a/tests/controller/controller_webpush_test.php
+++ b/tests/controller/controller_webpush_test.php
@@ -624,6 +624,83 @@ public function test_is_valid_endpoint(string $endpoint, bool $expected): void
$this->assertEquals($expected, $this->controller->is_valid_endpoint($endpoint));
}
+ public function test_current_notification_data_is_rendered(): void
+ {
+ $notification = $this->createMock(\phpbb\notification\type\type_interface::class);
+ $notification->method('users_to_query')->willReturn([]);
+ $notification->method('get_title')->willReturn('A & title');
+ $notification->method('get_reference')->willReturn('A "reference"');
+ $notification->method('get_url')->willReturn('viewtopic.php?p=1&x=2');
+ $notification->method('get_avatar')->willReturn('
');
+ $this->notification_manager->expects(self::once())
+ ->method('get_item_type_class')
+ ->with('notification.type.test', self::isType('array'))
+ ->willReturn($notification);
+
+ $method = new ReflectionMethod($this->controller, 'get_notification_data');
+ $method->setAccessible(true);
+ $data = json_decode($method->invoke($this->controller, json_encode([
+ 'notification_type_name' => 'notification.type.test',
+ ])), true);
+
+ self::assertSame('yourdomain.com', $data['heading']);
+ self::assertSame('A & title', $data['title']);
+ self::assertSame('A "reference"', $data['text']);
+ self::assertSame('viewtopic.php?p=1&x=2', $data['url']);
+ self::assertStringEndsWith('/images/avatar.png', $data['avatar']['src']);
+ }
+
+ public function subscription_write_data(): array
+ {
+ return [
+ 'legacy seconds' => [['expiration_time' => 42, 'keys' => ['p256dh' => 'key', 'auth' => 'auth']], 42],
+ 'empty browser timestamp' => [['expirationTime' => null, 'keys' => ['p256dh' => 'key', 'auth' => 'auth']], 0],
+ 'browser milliseconds' => [['expirationTime' => 42000, 'keys' => ['p256dh' => 'key', 'auth' => 'auth']], 42],
+ ];
+ }
+
+ /**
+ * @dataProvider subscription_write_data
+ */
+ public function test_subscription_write_data_normalizes_expiration(array $data, $expected): void
+ {
+ $method = new ReflectionMethod($this->controller, 'get_subscription_write_data');
+ $method->setAccessible(true);
+
+ self::assertSame($expected, $method->invoke($this->controller, $data)['expiration_time']);
+ }
+
+ public function test_subscription_write_data_rejects_non_array_keys(): void
+ {
+ $method = new ReflectionMethod($this->controller, 'get_subscription_write_data');
+ $method->setAccessible(true);
+
+ $this->expectException(http_exception::class);
+ $this->expectExceptionMessage('AJAX_ERROR_TEXT');
+ $method->invoke($this->controller, ['keys' => 'invalid']);
+ }
+
+ public function avatar_data(): array
+ {
+ return [
+ 'empty' => ['', ''],
+ 'plain url' => ['https://example.com/avatar.png', 'https://example.com/avatar.png'],
+ 'multiple sources use final source' => ['
', 'avatar.png'],
+ ];
+ }
+
+ /**
+ * @dataProvider avatar_data
+ */
+ public function test_prepare_avatar($avatar, $expected_suffix): void
+ {
+ $method = new ReflectionMethod($this->controller, 'prepare_avatar');
+ $method->setAccessible(true);
+ $actual = $method->invoke($this->controller, $avatar);
+
+ self::assertStringEndsWith($expected_suffix, $actual['src']);
+ }
+
public function test_toggle_popup_enable_to_disable()
{
$this->form_helper->method('check_form_tokens')->willReturn(true);
diff --git a/tests/event/listener_test.php b/tests/event/listener_test.php
index 084c58b..e343196 100644
--- a/tests/event/listener_test.php
+++ b/tests/event/listener_test.php
@@ -73,8 +73,6 @@ protected function setUp(): void
$form_request->enable_super_globals();
$this->request = $this->getMockBuilder('\phpbb\request\request_interface')
->getMock();
- $this->request->method('server')
- ->willReturn('');
$user = new \phpbb\user($this->language, '\phpbb\datetime');
$this->user = $user;
$this->user->data['user_form_salt'] = '';
@@ -359,6 +357,38 @@ public function test_pwa_manifest()
$dispatcher->trigger_event('core.acp_main_notice');
}
+ public function test_load_template_data_returns_when_notifications_unavailable()
+ {
+ $this->config['wpn_webpush_enable'] = false;
+ $this->user->data['user_id'] = ANONYMOUS;
+ $this->notifications->expects(self::never())->method('get_subscription_methods');
+
+ $this->set_listener();
+ $this->listener->load_template_data();
+ }
+
+ public function test_pwa_manifest_builds_fallback_name_and_mobile_banner()
+ {
+ $this->config['sitename'] = 'Long & Mobile Site';
+ $this->user->style = [
+ 'pwa_theme_color' => '#112233',
+ 'pwa_bg_color' => '#445566',
+ ];
+ $this->request->method('server')->with('HTTP_USER_AGENT', '')->willReturn('Mozilla/5.0 iPhone Mobile');
+
+ $this->template->expects(self::once())->method('assign_vars')->with([
+ 'U_MANIFEST_URL' => $this->controller_helper->route('phpbb_webpushnotifications_manifest_controller'),
+ 'U_TOUCH_ICON' => null,
+ 'SHORT_SITE_NAME' => 'Long & Mobil',
+ 'PWA_THEME_COLOR' => '#112233',
+ 'PWA_BG_COLOR' => '#445566',
+ 'S_PWA_SHOW_BANNER' => true,
+ ]);
+
+ $this->set_listener();
+ $this->listener->pwa_manifest();
+ }
+
public function test_wpn_faq()
{
$this->language->add_lang('webpushnotifications_faq', 'phpbb/webpushnotifications');
diff --git a/tests/form/form_helper_test.php b/tests/form/form_helper_test.php
new file mode 100644
index 0000000..7e965ff
--- /dev/null
+++ b/tests/form/form_helper_test.php
@@ -0,0 +1,85 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ */
+
+namespace phpbb\webpushnotifications\tests\form;
+
+class form_helper_test extends \phpbb_test_case
+{
+ protected $config;
+ protected $request;
+ protected $user;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->config = new \phpbb\config\config([
+ 'form_token_lifetime' => 60,
+ 'form_token_sid_guests' => true,
+ ]);
+ $this->request = $this->createMock(\phpbb\request\request_interface::class);
+ $this->user = $this->createMock(\phpbb\user::class);
+ $this->user->data = [
+ 'user_id' => 2,
+ 'user_form_salt' => 'salt',
+ ];
+ $this->user->session_id = 'session';
+ }
+
+ public function check_data(): array
+ {
+ return [
+ 'valid user token' => [2, 60, null, 10, true, true, true],
+ 'valid guest token with sid' => [ANONYMOUS, 60, null, 10, true, true, true],
+ 'unlimited lifetime' => [2, -1, null, 3600, true, true, true],
+ 'minimum lifetime enforced' => [2, 1, null, 31, true, true, false],
+ 'explicit lifetime' => [2, 60, 5, 10, true, true, false],
+ 'missing creation time' => [2, 60, null, 10, false, true, false],
+ 'missing token' => [2, 60, null, 10, true, false, false],
+ 'invalid token' => [2, 60, null, 10, true, true, false, 'invalid'],
+ ];
+ }
+
+ /**
+ * @dataProvider check_data
+ */
+ public function test_check_form_tokens($user_id, $lifetime, $timespan, $age, $has_time, $has_token, $expected, $token_override = null): void
+ {
+ $this->config['form_token_lifetime'] = $lifetime;
+ $this->user->data['user_id'] = $user_id;
+ $creation_time = time() - $age;
+ $token_sid = $user_id === ANONYMOUS ? $this->user->session_id : '';
+ $token = $token_override ?? sha1($creation_time . 'salt' . 'test-form' . $token_sid);
+
+ $this->request->method('is_set_post')->willReturnMap([
+ ['creation_time', $has_time],
+ ['form_token', $has_token],
+ ]);
+ $this->request->method('variable')->willReturnMap([
+ ['creation_time', 0, false, \phpbb\request\request_interface::REQUEST, $creation_time],
+ ['form_token', '', false, \phpbb\request\request_interface::REQUEST, $token],
+ ]);
+
+ $helper = new \phpbb\webpushnotifications\form\form_helper($this->config, $this->request, $this->user);
+ self::assertSame($expected, $helper->check_form_tokens('test-form', $timespan));
+ }
+
+ public function test_get_form_tokens_uses_guest_session(): void
+ {
+ $this->user->data['user_id'] = ANONYMOUS;
+ $helper = new \phpbb\webpushnotifications\form\form_helper($this->config, $this->request, $this->user);
+
+ $tokens = $helper->get_form_tokens('test-form', $now, $token_sid, $token);
+
+ self::assertSame('session', $token_sid);
+ self::assertSame(sha1($now . 'salt' . 'test-form' . 'session'), $token);
+ self::assertSame(['creation_time' => $now, 'form_token' => $token], $tokens);
+ }
+}
diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php
index 69e8042..4d79d2b 100644
--- a/tests/notification/notification_method_webpush_test.php
+++ b/tests/notification/notification_method_webpush_test.php
@@ -645,6 +645,61 @@ public function test_get_type(): void
$this->assertEquals('notification.method.phpbb.wpn.webpush', $this->notification_method_webpush->get_type());
}
+ public function test_availability_and_default_setting(): void
+ {
+ $this->config['wpn_webpush_enable'] = true;
+ $this->config['wpn_webpush_method_enabled'] = true;
+ self::assertTrue($this->notification_method_webpush->is_available());
+ self::assertTrue($this->notification_method_webpush->is_enabled_by_default());
+
+ $this->config['wpn_webpush_vapid_private'] = '';
+ self::assertFalse($this->notification_method_webpush->is_available());
+ $this->config['wpn_webpush_method_enabled'] = false;
+ self::assertFalse($this->notification_method_webpush->is_enabled_by_default());
+ }
+
+ public function mark_data(): array
+ {
+ return [
+ 'all filters' => [1, 2, 3],
+ 'array filters' => [[1, 2], [2, 3], [3, 4]],
+ 'no filters' => [false, false, false],
+ ];
+ }
+
+ /**
+ * @dataProvider mark_data
+ */
+ public function test_mark_notifications($type_id, $item_id, $user_id): void
+ {
+ $this->notification_method_webpush->mark_notifications($type_id, $item_id, $user_id);
+ self::assertTrue(true);
+ }
+
+ /**
+ * @dataProvider mark_data
+ */
+ public function test_mark_notifications_by_parent($type_id, $parent_id, $user_id): void
+ {
+ $this->notification_method_webpush->mark_notifications_by_parent($type_id, $parent_id, $user_id);
+ self::assertTrue(true);
+ }
+
+ public function test_set_endpoint_padding_ignores_library_exception(): void
+ {
+ $web_push = $this->getMockBuilder(\Minishlink\WebPush\WebPush::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['setAutomaticPadding'])
+ ->getMock();
+ $web_push->expects(self::once())->method('setAutomaticPadding')->willThrowException(new \Exception('padding'));
+
+ $method = new \ReflectionMethod($this->notification_method_webpush, 'set_endpoint_padding');
+ $method->setAccessible(true);
+ $method->invoke($this->notification_method_webpush, $web_push, 'https://push.mozilla.com/test');
+
+ self::assertTrue(true);
+ }
+
public function test_push_token_map_is_per_user(): void
{
// Verifies that when multiple users are notified about the same item,
diff --git a/tests/notification/webpush_branches_test.php b/tests/notification/webpush_branches_test.php
new file mode 100644
index 0000000..d917cf0
--- /dev/null
+++ b/tests/notification/webpush_branches_test.php
@@ -0,0 +1,149 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ */
+
+namespace phpbb\webpushnotifications\notification\method
+{
+ function phpbb_get_banned_user_ids($user_ids)
+ {
+ return $GLOBALS['phpbb_wpn_test_banned_users'] ?? \phpbb_get_banned_user_ids($user_ids);
+ }
+}
+
+namespace phpbb\webpushnotifications\tests\notification
+{
+ class webpush_branches_test extends \phpbb_test_case
+ {
+ protected function tearDown(): void
+ {
+ unset($GLOBALS['phpbb_wpn_test_banned_users']);
+ parent::tearDown();
+ }
+
+ public function test_all_banned_users_empty_queue_before_delivery(): void
+ {
+ $GLOBALS['phpbb_wpn_test_banned_users'] = [2];
+ $method = $this->create_method();
+ $notification = $this->create_notification(2);
+ $method->add_to_queue($notification);
+
+ $method->send_queued_notifications();
+
+ self::assertSame([], $method->get_queue());
+ }
+
+ public function test_invalid_subscription_is_removed_and_logged(): void
+ {
+ $GLOBALS['phpbb_wpn_test_banned_users'] = [];
+ $log = $this->createMock(\phpbb\log\log_interface::class);
+ $log->expects(self::once())->method('add')->with(
+ 'user',
+ 2,
+ '127.0.0.1',
+ 'LOG_WEBPUSH_SUBSCRIPTION_REMOVED'
+ );
+ $user_loader = $this->createMock(\phpbb\user_loader::class);
+ $user_loader->method('get_user')->with(2)->willReturn([
+ 'user_id' => 2,
+ 'user_type' => USER_NORMAL,
+ 'user_inactive_reason' => 0,
+ 'user_form_salt' => 'salt',
+ 'user_ip' => '127.0.0.1',
+ 'username' => 'Tester',
+ ]);
+ $method = $this->create_method($log, $user_loader, str_repeat('x', 5000));
+ $method->subscriptions = [
+ 2 => [[
+ 'subscription_id' => 9,
+ 'endpoint' => 'https://updates.push.services.mozilla.com/test',
+ 'p256dh' => 'invalid',
+ 'auth' => 'invalid',
+ ]],
+ ];
+ $method->add_to_queue($this->create_notification(2));
+ $method->set_push_token(1, 10, 2, 'token');
+
+ $method->send_queued_notifications();
+
+ self::assertSame([9], $method->removed_subscriptions);
+ }
+
+ private function create_method($log = null, $user_loader = null, $assets_version = 1): testable_webpush
+ {
+ global $config, $phpbb_root_path, $phpEx, $user;
+
+ $config = new \phpbb\config\config([
+ 'wpn_webpush_vapid_public' => notification_method_webpush_test::VAPID_KEYS['publicKey'],
+ 'wpn_webpush_vapid_private' => notification_method_webpush_test::VAPID_KEYS['privateKey'],
+ 'assets_version' => $assets_version,
+ 'force_server_vars' => true,
+ 'server_protocol' => 'https://',
+ 'server_name' => 'example.com',
+ 'server_port' => 443,
+ 'script_path' => '',
+ 'cookie_secure' => true,
+ ]);
+ $user = $this->createMock(\phpbb\user::class);
+
+ return new testable_webpush(
+ $config,
+ $this->createMock(\phpbb\db\driver\driver_interface::class),
+ $log ?: $this->createMock(\phpbb\log\log_interface::class),
+ $user_loader ?: $this->createMock(\phpbb\user_loader::class),
+ $user,
+ $phpbb_root_path,
+ $phpEx,
+ 'phpbb_wpn_notification_push',
+ 'phpbb_wpn_push_subscriptions'
+ );
+ }
+
+ private function create_notification($user_id)
+ {
+ $notification = $this->createMock(\phpbb\notification\type\type_interface::class);
+ $notification->user_id = $user_id;
+ $notification->notification_type_id = 1;
+ $notification->item_id = 10;
+ return $notification;
+ }
+ }
+
+ class testable_webpush extends \phpbb\webpushnotifications\notification\method\webpush
+ {
+ public $subscriptions = [];
+ public $removed_subscriptions = [];
+
+ public function send_queued_notifications(): void
+ {
+ $this->notify_using_webpush();
+ }
+
+ public function get_queue(): array
+ {
+ return $this->queue;
+ }
+
+ public function set_push_token($type_id, $item_id, $user_id, $token): void
+ {
+ $property = new \ReflectionProperty(\phpbb\webpushnotifications\notification\method\webpush::class, 'push_token_map');
+ $property->setAccessible(true);
+ $property->setValue($this, [$type_id => [$item_id => [$user_id => $token]]]);
+ }
+
+ protected function get_user_subscription_map(array $notify_users): array
+ {
+ return $this->subscriptions;
+ }
+
+ public function remove_subscriptions(array $subscription_ids): void
+ {
+ $this->removed_subscriptions = $subscription_ids;
+ }
+ }
+}
diff --git a/tests/system/simple_test.php b/tests/system/simple_test.php
index 82c892b..2baf7ea 100644
--- a/tests/system/simple_test.php
+++ b/tests/system/simple_test.php
@@ -50,4 +50,37 @@ public function test_ext()
self::assertTrue($ext->is_enableable(), 'Asserting that the extension is enable-able.');
}
+
+ public function test_enable_errors_are_localised()
+ {
+ $language = $this->createMock(\phpbb\language\language::class);
+ $language->expects(self::once())->method('add_lang')->with('install', 'phpbb/webpushnotifications');
+ $language->method('lang')->willReturnCallback(function () {
+ return implode(':', func_get_args());
+ });
+ $this->container->method('get')->with('language')->willReturn($language);
+
+ $ext = new testable_ext($this->container, $this->extension_finder, $this->migrator, 'phpbb/webpushnotifications', '');
+ $ext->set_errors(['PHPBB_VERSION_MIN_ERROR', ['PHP_EXT_MISSING', 'curl']]);
+
+ self::assertSame(['PHPBB_VERSION_MIN_ERROR', 'PHP_EXT_MISSING:curl'], $ext->get_result());
+ }
+
+ public function test_decode_entities()
+ {
+ self::assertSame('A&B "quote"', \phpbb\webpushnotifications\ext::decode_entities('A&B "quote"', ENT_QUOTES));
+ }
+}
+
+class testable_ext extends \phpbb\webpushnotifications\ext
+{
+ public function set_errors(array $errors): void
+ {
+ $this->errors = $errors;
+ }
+
+ public function get_result()
+ {
+ return $this->result();
+ }
}