BxBaseInterfaceSoundsTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. <?php
  2. /**
  3. * Interface sounds (Cuelume, plugins_public/cuelume): Settings > Sounds (BxDolSounds) decides where they play and how,
  4. * form controls and tab menus carry the data-cuelume-* attributes its bind() plays, and the visitor's Sounds control
  5. * follows the settings.
  6. */
  7. class BxBaseInterfaceSoundsTest extends BxDolTestCase
  8. {
  9. private function withOption(string $sName, string $sValue, callable $fTest)
  10. {
  11. $sPrior = getParam($sName);
  12. setParam($sName, $sValue);
  13. try {
  14. return $fTest();
  15. }
  16. finally {
  17. setParam($sName, $sPrior);
  18. }
  19. }
  20. private function callProtected($oObject, string $sMethod, array $aArgs = [])
  21. {
  22. $oMethod = new ReflectionMethod($oObject, $sMethod);
  23. return $oMethod->invokeArgs($oObject, $aArgs);
  24. }
  25. private function withOptions(array $aOptions, callable $fTest)
  26. {
  27. if (!$aOptions)
  28. return $fTest();
  29. $sName = array_key_first($aOptions);
  30. $sValue = $aOptions[$sName];
  31. unset($aOptions[$sName]);
  32. return $this->withOption($sName, $sValue, fn() => $this->withOptions($aOptions, $fTest));
  33. }
  34. public function testHeadInjectionFollowsTheSitePlatform(): void
  35. {
  36. $oFunctions = BxTemplFunctions::getInstance();
  37. $sOn = $this->withOption('sys_sounds_platforms', 'site,ios', fn() => $this->callProtected($oFunctions, 'getInjHeadSounds'));
  38. $this->assertStringContainsString("bx_sound_init('" . BX_DOL_URL_PLUGINS_PUBLIC . "cuelume/cuelume.min.js', {", $sOn);
  39. $this->assertStringContainsString('"on":true', $sOn);
  40. $this->assertStringNotContainsString('"platforms"', $sOn);
  41. $sOff = $this->withOption('sys_sounds_platforms', 'studio,neo_web', fn() => $this->callProtected($oFunctions, 'getInjHeadSounds'));
  42. $this->assertSame('', $sOff);
  43. }
  44. public function testStudioKeepsItsPreviewsWhileSilent(): void
  45. {
  46. $oFunctions = BxTemplStudioFunctions::getInstance();
  47. $sOff = $this->withOption('sys_sounds_platforms', 'site', fn() => $this->callProtected($oFunctions, 'getInjHeadSounds'));
  48. $this->assertStringContainsString('bx_sound_init(', $sOff);
  49. $this->assertStringContainsString('"on":false', $sOff);
  50. $sOn = $this->withOption('sys_sounds_platforms', 'studio', fn() => $this->callProtected($oFunctions, 'getInjHeadSounds'));
  51. $this->assertStringContainsString('"on":true', $sOn);
  52. }
  53. public function testConfigListsOnlyWhatDiffersFromTheMainSet(): void
  54. {
  55. $aConfig = $this->withOptions([
  56. 'sys_sounds_platforms' => 'ios,site,nowhere',
  57. 'sys_sounds_level' => 'loud',
  58. 'sys_sounds_set' => 'mech',
  59. 'sys_sounds_volume' => '50',
  60. 'sys_sounds_buttons' => 'off',
  61. 'sys_sounds_choices' => 'mech',
  62. 'sys_sounds_reactions' => 'bubble',
  63. 'sys_sounds_menus' => 'nonsense',
  64. 'sys_sounds_navigation' => '',
  65. ], fn() => BxDolSounds::getInstance()->getConfig());
  66. $this->assertSame(['site', 'ios'], $aConfig['platforms']);
  67. $this->assertSame('full', $aConfig['level'], 'An unknown level starts people at full.');
  68. $this->assertSame('mech', $aConfig['set']);
  69. $this->assertSame(0.5, $aConfig['volume']);
  70. $this->assertSame(['buttons' => 'off', 'reactions' => 'bubble'], (array)$aConfig['groups']);
  71. $this->assertSame('{}', json_encode($this->withOptions(['sys_sounds_buttons' => '', 'sys_sounds_reactions' => ''], fn() => BxDolSounds::getInstance()->getConfig()['groups'])), 'Apps get an object, not a list.');
  72. }
  73. public function testEachKindOfActionOffersTheMainSetEverySetAndSilence(): void
  74. {
  75. $aValues = BxDolService::call('system', 'get_options_sounds_group', [], 'TemplServices');
  76. $this->assertSame(array_merge([''], BxDolSounds::SETS, ['off']), array_map('strval', array_keys($aValues)));
  77. $this->assertSame([100, 75, 50, 25], array_keys(BxDolService::call('system', 'get_options_sounds_volume', [], 'TemplServices')));
  78. }
  79. public function testSoundsSelectsPreviewInStudio(): void
  80. {
  81. $oOptions = new BxTemplStudioOptions('system', 'sounds');
  82. $aSelect = ['name' => 'sys_sounds_reactions', 'type' => 'select', 'caption' => '', 'info' => '', 'value' => '', 'extra' => 'a:3:{s:6:"module";s:6:"system";s:6:"method";s:24:"get_options_sounds_group";s:5:"class";s:13:"TemplServices";}'];
  83. $aField = $this->callProtected($oOptions, 'field', [$aSelect, []]);
  84. $this->assertSame('bx_sound_preview(this)', $aField['attrs']['onchange']);
  85. $this->assertSame('reactions', $aField['attrs']['data-bx-sound-option']);
  86. $aOther = $this->callProtected($oOptions, 'field', [array_merge($aSelect, ['name' => 'sys_vote_reactions_mode', 'extra' => 'a,b']), []]);
  87. $this->assertArrayNotHasKey('onchange', $aOther['attrs']);
  88. }
  89. private function genInput(array $aInput): string
  90. {
  91. $oForm = new BxTemplFormView(['form_attrs' => ['id' => 'bx-sounds-test-form', 'method' => 'post'], 'inputs' => []]);
  92. return $oForm->genInput($aInput);
  93. }
  94. public function testSubmitTapsAndOtherButtonsTapSubtly(): void
  95. {
  96. $sSubmit = $this->genInput(['type' => 'submit', 'name' => 'save', 'value' => 'Save']);
  97. $this->assertStringContainsString('data-cuelume-tap=""', $sSubmit);
  98. $this->assertStringNotContainsString('data-cuelume-emphasis', $sSubmit);
  99. $sButton = $this->genInput(['type' => 'button', 'name' => 'more', 'value' => 'More']);
  100. $this->assertStringContainsString('data-cuelume-tap=""', $sButton);
  101. $this->assertStringContainsString('data-cuelume-emphasis="subtle"', $sButton);
  102. }
  103. public function testCheckboxTogglesAndRadioSelects(): void
  104. {
  105. $this->assertStringContainsString('data-cuelume-toggle=""', $this->genInput(['type' => 'checkbox', 'name' => 'agree', 'value' => '1']));
  106. $this->assertStringContainsString('data-cuelume-select=""', $this->genInput(['type' => 'radio', 'name' => 'pick', 'value' => '1']));
  107. }
  108. public function testSwitcherTogglesFromItsButtonOnly(): void
  109. {
  110. $sSwitcher = $this->genInput(['type' => 'switcher', 'name' => 'active', 'value' => '1', 'checked' => true]);
  111. // The visible button toggles; the hidden checkbox behind it must not sound a second time.
  112. $this->assertSame(1, substr_count($sSwitcher, 'data-cuelume-toggle'));
  113. $this->assertMatchesRegularExpression('/<button[^>]*role="switch"[^>]*data-cuelume-toggle/', $sSwitcher);
  114. }
  115. public function testFieldKeepsTheCueItPicked(): void
  116. {
  117. $sButton = $this->genInput(['type' => 'submit', 'name' => 'delete', 'value' => 'Delete', 'attrs' => ['data-cuelume-close' => '', 'data-cuelume-emphasis' => 'strong']]);
  118. $this->assertStringContainsString('data-cuelume-close=""', $sButton);
  119. $this->assertStringContainsString('data-cuelume-emphasis="strong"', $sButton);
  120. $this->assertStringNotContainsString('data-cuelume-tap', $sButton);
  121. }
  122. private function menuCode(string $sTemplate): string
  123. {
  124. $aItem = ['active' => 1, 'visible_for_levels' => 2147483647, 'target' => '', 'onclick' => '', 'icon' => '', 'addon' => '', 'submenu_object' => ''];
  125. $oMenu = new BxTemplMenu([
  126. 'object' => 'bx_sounds_test_menu',
  127. 'template' => $sTemplate,
  128. 'menu_items' => [
  129. array_merge($aItem, ['id' => 'latest', 'name' => 'latest', 'title' => 'Latest', 'link' => 'page.php?i=latest']),
  130. array_merge($aItem, ['id' => 'more-auto', 'name' => 'more-auto', 'title' => 'More', 'link' => 'javascript:void(0)']),
  131. ],
  132. ], BxDolTemplate::getInstance());
  133. return $oMenu->getCode();
  134. }
  135. public function testTabMenusSelectButNotTheirMoreItem(): void
  136. {
  137. $sTabs = $this->menuCode('menu_main_submenu.html');
  138. $this->assertSame(1, substr_count($sTabs, 'data-cuelume-select'), 'Only the tab selects; More opens a popup, which sounds itself.');
  139. $this->assertStringNotContainsString('data-cuelume-select', $this->menuCode('menu_vertical.html'));
  140. }
  141. public function testSoundsItemFollowsTheSitePlatform(): void
  142. {
  143. $oMenu = BxDolMenu::getObjectInstance('sys_account_notifications');
  144. if(!$oMenu)
  145. $this->markTestSkipped('sys_account_notifications is not installed.');
  146. $aItem = ['id' => 0, 'name' => 'sounds', 'module' => 'system', 'title' => '_sys_menu_item_title_sounds', 'link' => 'javascript:void(0);', 'onclick' => 'bx_sound_toggle(this);', 'target' => '', 'icon' => 'volume-up', 'addon' => '', 'submenu_object' => '', 'visible_for_levels' => 2147483647, 'active' => 1];
  147. $this->assertTrue($this->withOption('sys_sounds_platforms', 'site', fn() => $this->callProtected($oMenu, '_isVisible', [$aItem])));
  148. $this->assertFalse($this->withOption('sys_sounds_platforms', 'studio,ios', fn() => $this->callProtected($oMenu, '_isVisible', [$aItem])));
  149. $aRendered = $this->withOption('sys_sounds_platforms', 'site', fn() => $this->callProtected($oMenu, '_getMenuItem', [$aItem]));
  150. $this->assertIsArray($aRendered);
  151. $this->assertStringContainsString('role="button"', $aRendered['attrs'], 'Three levels: a button that steps through them, not a switch.');
  152. $this->assertStringContainsString('data-bx-sounds-switch="1"', $aRendered['attrs']);
  153. }
  154. public function testStudioSoundsItemIsALevelRadioGroup(): void
  155. {
  156. $oMenu = BxDolMenu::getObjectInstance('sys_studio_account_popup');
  157. if(!$oMenu)
  158. $this->markTestSkipped('sys_studio_account_popup is not installed.');
  159. $aItem = ['id' => 0, 'name' => 'sounds', 'module' => 'system', 'title' => '_sys_menu_item_title_sa_sounds', 'link' => 'javascript:void(0)', 'onclick' => 'bx_sound_toggle(this);', 'target' => '', 'icon' => 'ami-sounds.svg', 'addon' => '', 'submenu_object' => '', 'visible_for_levels' => 2147483647, 'active' => 1];
  160. $this->assertFalse($this->withOption('sys_sounds_platforms', 'site', fn() => $this->callProtected($oMenu, '_getMenuItem', [$aItem])));
  161. $aRendered = $this->withOption('sys_sounds_platforms', 'studio', fn() => $this->callProtected($oMenu, '_getMenuItem', [$aItem]));
  162. $this->assertTrue($aRendered['bx_if:show_sounds']['condition']);
  163. $this->assertFalse($aRendered['bx_if:show_button']['condition']);
  164. $this->assertSame(_t('_sys_txt_sounds_level_quiet'), $aRendered['bx_if:show_sounds']['content']['title_quiet']);
  165. }
  166. public function testStudioReplyCarriesItsCue(): void
  167. {
  168. if (!function_exists('bx_get_js_result'))
  169. require_once(BX_DIRECTORY_PATH_ROOT . 'studio/inc/utils.inc.php');
  170. $sReply = bx_get_js_result(['code' => 1, 'cue' => 'error', 'message' => 'x', 'translate' => false]);
  171. $this->assertStringContainsString('"cue":"error"', $sReply);
  172. $sReply = bx_get_js_result(['code' => 0, 'cue' => ['cue' => 'success', 'emphasis' => 'subtle']]);
  173. $this->assertStringContainsString('"cue":{"cue":"success","emphasis":"subtle"}', $sReply);
  174. }
  175. public function testFreshInstallDeclaresTheSoundsSection(): void
  176. {
  177. $sSql = file_get_contents(__DIR__ . '/../../../../install/sql/system.sql');
  178. $this->assertNotFalse($sSql);
  179. $this->assertStringContainsString("(@iTypeId, 'sounds', '_adm_stg_cpt_category_sounds', 0, 24)", $sSql);
  180. $this->assertStringContainsString("'sys_sounds_platforms', '_adm_stg_cpt_option_sys_sounds_platforms', '_adm_stg_inf_option_sys_sounds_platforms', 'site,studio,neo_web,ios,android', 'list',", $sSql);
  181. foreach (BxDolSounds::GROUPS as $sGroup)
  182. $this->assertMatchesRegularExpression("/\\(@iCategoryId, 'sys_sounds_" . $sGroup . "', '_adm_stg_cpt_option_sys_sounds_" . $sGroup . "', '_adm_stg_inf_option_sys_sounds_" . $sGroup . "', '', 'select',/", $sSql);
  183. $this->assertStringNotContainsString('sys_ui_sounds', $sSql);
  184. $this->assertStringContainsString("('sys_account_notifications', 'system', 'sounds',", $sSql);
  185. $this->assertStringContainsString("('sys_studio_account_popup', 'system', 'sounds',", $sSql);
  186. }
  187. }