feat: add more keyboard shortcuts (#904)
* feat: add more keyboard shortcuts largely fixes #895 * oops wrong test name
This commit is contained in:
		
							parent
							
								
									b014778761
								
							
						
					
					
						commit
						45d70e8e6b
					
				
					 8 changed files with 184 additions and 43 deletions
				
			
		
							
								
								
									
										8
									
								
								src/routes/_actions/mention.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/routes/_actions/mention.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | import { importShowComposeDialog } from '../_components/dialog/asyncDialogs' | ||||||
|  | import { store } from '../_store/store' | ||||||
|  | 
 | ||||||
|  | export async function composeNewStatusMentioning (account) { | ||||||
|  |   store.setComposeData('dialog', { text: `@${account.acct} ` }) | ||||||
|  |   let showComposeDialog = await importShowComposeDialog() | ||||||
|  |   showComposeDialog() | ||||||
|  | } | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| <Shortcut key="g c" on:pressed="goto('/community')"/> | <Shortcut key="g c" on:pressed="goto('/community')"/> | ||||||
| <Shortcut key="s" on:pressed="goto('/search')"/> | <Shortcut key="s" on:pressed="goto('/search')"/> | ||||||
| <Shortcut key="h|?" on:pressed="showShortcutHelpDialog()"/> | <Shortcut key="h|?" on:pressed="showShortcutHelpDialog()"/> | ||||||
|  | <Shortcut key="c" on:pressed="showComposeDialog()"/> | ||||||
| {#each $navPages as navPage, i} | {#each $navPages as navPage, i} | ||||||
|   <Shortcut key={(i + 1).toString()} on:pressed="goto(navPage.href)" /> |   <Shortcut key={(i + 1).toString()} on:pressed="goto(navPage.href)" /> | ||||||
| {/each} | {/each} | ||||||
|  | @ -13,7 +14,7 @@ | ||||||
| <script> | <script> | ||||||
|   import Shortcut from './shortcut/Shortcut' |   import Shortcut from './shortcut/Shortcut' | ||||||
|   import { goto } from '../../../__sapper__/client' |   import { goto } from '../../../__sapper__/client' | ||||||
|   import { importShowShortcutHelpDialog } from './dialog/asyncDialogs' |   import { importShowShortcutHelpDialog, importShowComposeDialog } from './dialog/asyncDialogs' | ||||||
|   import { store } from '../_store/store' |   import { store } from '../_store/store' | ||||||
| 
 | 
 | ||||||
|   export default { |   export default { | ||||||
|  | @ -26,6 +27,10 @@ | ||||||
|       async showShortcutHelpDialog () { |       async showShortcutHelpDialog () { | ||||||
|         let showShortcutHelpDialog = await importShowShortcutHelpDialog() |         let showShortcutHelpDialog = await importShowShortcutHelpDialog() | ||||||
|         showShortcutHelpDialog() |         showShortcutHelpDialog() | ||||||
|  |       }, | ||||||
|  |       async showComposeDialog () { | ||||||
|  |         let showComposeDialog = await importShowComposeDialog() | ||||||
|  |         showComposeDialog() | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -1,30 +1,58 @@ | ||||||
| <div class="{inDialog ? 'in-dialog' : ''}"> | <div class="shortcut-help-info {inDialog ? 'in-dialog' : ''}"> | ||||||
|   <ul> |   <h2>Global</h2> | ||||||
|     <li><kbd>s</kbd> to search</li> |   <div class="hotkey-group"> | ||||||
|     <li><kbd>1</kbd> - <kbd>6</kbd> to switch columns</li> |     <ul> | ||||||
|     <li><kbd>g</kbd> + <kbd>h</kbd> to go home</li> |       <li><kbd>c</kbd> to compose a new toot</li> | ||||||
|     <li><kbd>g</kbd> + <kbd>n</kbd> to go to the notifications page</li> |       <li><kbd>s</kbd> to search</li> | ||||||
|     <li><kbd>g</kbd> + <kbd>l</kbd> to go to the local stream page</li> |       <li><kbd>1</kbd> - <kbd>6</kbd> to switch columns</li> | ||||||
|     <li><kbd>g</kbd> + <kbd>t</kbd> to go to the federated stream page</li> |       <li><kbd>g</kbd> + <kbd>h</kbd> to go home</li> | ||||||
|     <li><kbd>g</kbd> + <kbd>c</kbd> to go to the community page</li> |       <li><kbd>g</kbd> + <kbd>n</kbd> to go to notifications</li> | ||||||
|     <li><kbd>j</kbd> or <kbd>↓</kbd> to activate the next status</li> |       <li><kbd>g</kbd> + <kbd>l</kbd> to go to the local timeline</li> | ||||||
|     <li><kbd>k</kbd> or <kbd>↑</kbd> to activate the previous status</li> |       <li><kbd>g</kbd> + <kbd>t</kbd> to go to the federated timeline</li> | ||||||
|     <li><kbd>o</kbd> to open the active status</li> |       <li><kbd>g</kbd> + <kbd>c</kbd> to go to the community page</li> | ||||||
|     <li><kbd>f</kbd> to favorite the active status</li> |       <li><kbd>h</kbd> or <kbd>?</kbd> to toggle the help dialog</li> | ||||||
|     <li><kbd>b</kbd> to boost the active status</li> |       <li><kbd>Backspace</kbd> to go back, close dialogs</li> | ||||||
|     <li><kbd>r</kbd> to reply to the active status</li> |     </ul> | ||||||
|     <li><kbd>x</kbd> to show or hide text behind content warning in the active status</li> |   </div> | ||||||
|     <li><kbd>y</kbd> to show or hide sensitive media in the active status</li> |   <h2>On an active toot</h2> | ||||||
|     <li><kbd>h</kbd> or <kbd>?</kbd> to toggle the help dialog</li> |   <div class="hotkey-group"> | ||||||
|     <li><kbd>Backspace</kbd> to go back, close dialogs</li> |     <ul> | ||||||
|   </ul> |       <li><kbd>o</kbd> to open the thread</li> | ||||||
|  |       <li><kbd>f</kbd> to favorite</li> | ||||||
|  |       <li><kbd>b</kbd> to boost</li> | ||||||
|  |       <li><kbd>r</kbd> to reply</li> | ||||||
|  |       <li><kbd>m</kbd> to mention the author</li> | ||||||
|  |       <li><kbd>p</kbd> to open the author's profile</li> | ||||||
|  |       <li><kbd>x</kbd> to show or hide text behind content warning</li> | ||||||
|  |       <li><kbd>y</kbd> to show or hide sensitive media</li> | ||||||
|  |       <li><kbd>j</kbd> or <kbd>↓</kbd> to activate the next toot</li> | ||||||
|  |       <li><kbd>k</kbd> or <kbd>↑</kbd> to activate the previous toot</li> | ||||||
|  |     </ul> | ||||||
|  |   </div> | ||||||
| </div> | </div> | ||||||
| <style> | <style> | ||||||
|  |   .shortcut-help-info { | ||||||
|  |     overflow-y: scroll; | ||||||
|  |     scrollbar-width: none; | ||||||
|  |   } | ||||||
|  |   .shortcut-help-info::-webkit-scrollbar { | ||||||
|  |     display: none; | ||||||
|  |   } | ||||||
|   li { |   li { | ||||||
|     list-style-type: none; |     list-style-type: none; | ||||||
|   } |   } | ||||||
|   .in-dialog li { |   .in-dialog li { | ||||||
|     color: var(--muted-modal-text); |     color: var(--muted-modal-text); | ||||||
|  |     font-size: 0.9em; | ||||||
|  |   } | ||||||
|  |   .in-dialog h2 { | ||||||
|  |     color: var(--muted-modal-text); | ||||||
|  |   } | ||||||
|  |   .hotkey-group { | ||||||
|  |     margin: 0 0 10px 10px; | ||||||
|  |   } | ||||||
|  |   .shortcut-help-info h2 { | ||||||
|  |     margin: 10px 0; | ||||||
|   } |   } | ||||||
|   kbd { |   kbd { | ||||||
|     color: #333; |     color: #333; | ||||||
|  |  | ||||||
|  | @ -10,7 +10,6 @@ | ||||||
| import ModalDialog from './ModalDialog.html' | import ModalDialog from './ModalDialog.html' | ||||||
| import { store } from '../../../_store/store' | import { store } from '../../../_store/store' | ||||||
| import GenericDialogList from './GenericDialogList.html' | import GenericDialogList from './GenericDialogList.html' | ||||||
| import { importShowComposeDialog } from '../asyncDialogs' |  | ||||||
| import { createDialogId } from '../helpers/createDialogId' | import { createDialogId } from '../helpers/createDialogId' | ||||||
| import { show } from '../helpers/showDialog' | import { show } from '../helpers/showDialog' | ||||||
| import { close } from '../helpers/closeDialog' | import { close } from '../helpers/closeDialog' | ||||||
|  | @ -21,6 +20,7 @@ import { setAccountFollowed } from '../../../_actions/follow' | ||||||
| import { setShowReblogs } from '../../../_actions/setShowReblogs' | import { setShowReblogs } from '../../../_actions/setShowReblogs' | ||||||
| import { setDomainBlocked } from '../../../_actions/setDomainBlocked' | import { setDomainBlocked } from '../../../_actions/setDomainBlocked' | ||||||
| import { copyText } from '../../../_actions/copyText' | import { copyText } from '../../../_actions/copyText' | ||||||
|  | import { composeNewStatusMentioning } from '../../../_actions/mention' | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|   oncreate, |   oncreate, | ||||||
|  | @ -140,12 +140,8 @@ export default { | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     async onMentionClicked () { |     async onMentionClicked () { | ||||||
|       let { acct } = this.get() |       let { account } = this.get() | ||||||
|       this.store.setComposeData('dialog', { |       await composeNewStatusMentioning(account) | ||||||
|         text: `@${acct} ` |  | ||||||
|       }) |  | ||||||
|       let showComposeDialog = await importShowComposeDialog() |  | ||||||
|       showComposeDialog() |  | ||||||
|       this.close() |       this.close() | ||||||
|     }, |     }, | ||||||
|     async onFollowClicked () { |     async onFollowClicked () { | ||||||
|  |  | ||||||
|  | @ -3,14 +3,19 @@ | ||||||
|           {status} {notification} {active} {shortcutScope} on:recalculateHeight |           {status} {notification} {active} {shortcutScope} on:recalculateHeight | ||||||
|   /> |   /> | ||||||
| {:else} | {:else} | ||||||
|   <article class="notification-article {active ? 'active' : ''}" |   <article class="notification-article {active ? 'status-active' : ''}" | ||||||
|            tabindex="0" |            tabindex="0" | ||||||
|            aria-posinset={index} |            aria-posinset={index} | ||||||
|            aria-setsize={length} |            aria-setsize={length} | ||||||
|            aria-label={ariaLabel} |            aria-label={ariaLabel} | ||||||
|  |            status-active={active} | ||||||
|   > |   > | ||||||
|     <StatusHeader {notification} {notificationId} {status} {statusId} {timelineType} |     <StatusHeader {notification} {notificationId} {status} {statusId} {timelineType} | ||||||
|                   {account} {accountId} {uuid} isStatusInNotification="true" /> |                   {account} {accountId} {uuid} isStatusInNotification="true" /> | ||||||
|  |     {#if shortcutScope} | ||||||
|  |       <Shortcut scope={shortcutScope} key="p" on:pressed="openAuthorProfile()" /> | ||||||
|  |       <Shortcut scope={shortcutScope} key="m" on:pressed="mentionAuthor()" /> | ||||||
|  |     {/if} | ||||||
|   </article> |   </article> | ||||||
| {/if} | {/if} | ||||||
| <style> | <style> | ||||||
|  | @ -20,7 +25,7 @@ | ||||||
|     padding: 10px 20px; |     padding: 10px 20px; | ||||||
|     border-bottom: 1px solid var(--main-border); |     border-bottom: 1px solid var(--main-border); | ||||||
|   } |   } | ||||||
|   .notification-article.active { |   .notification-article.status-active { | ||||||
|     background-color: var(--status-active-background); |     background-color: var(--status-active-background); | ||||||
|   } |   } | ||||||
|   @media (max-width: 767px) { |   @media (max-width: 767px) { | ||||||
|  | @ -34,14 +39,22 @@ | ||||||
| <script> | <script> | ||||||
|   import Status from './Status.html' |   import Status from './Status.html' | ||||||
|   import StatusHeader from './StatusHeader.html' |   import StatusHeader from './StatusHeader.html' | ||||||
|  |   import Shortcut from '../shortcut/Shortcut.html' | ||||||
|   import { store } from '../../_store/store' |   import { store } from '../../_store/store' | ||||||
|   import { getAccountAccessibleName } from '../../_a11y/getAccountAccessibleName' |   import { getAccountAccessibleName } from '../../_a11y/getAccountAccessibleName' | ||||||
|  |   import { goto } from '../../../../__sapper__/client' | ||||||
|  |   import { composeNewStatusMentioning } from '../../_actions/mention' | ||||||
| 
 | 
 | ||||||
|   export default { |   export default { | ||||||
|     components: { |     components: { | ||||||
|       Status, |       Status, | ||||||
|       StatusHeader |       StatusHeader, | ||||||
|  |       Shortcut | ||||||
|     }, |     }, | ||||||
|  |     data: () => ({ | ||||||
|  |       active: false, | ||||||
|  |       shortcutScope: null | ||||||
|  |     }), | ||||||
|     store: () => store, |     store: () => store, | ||||||
|     computed: { |     computed: { | ||||||
|       account: ({ notification }) => notification.account, |       account: ({ notification }) => notification.account, | ||||||
|  | @ -55,6 +68,16 @@ | ||||||
|       ariaLabel: ({ status, account, $omitEmojiInDisplayNames }) => ( |       ariaLabel: ({ status, account, $omitEmojiInDisplayNames }) => ( | ||||||
|         !status && `${getAccountAccessibleName(account, $omitEmojiInDisplayNames)} followed you, @${account.acct}` |         !status && `${getAccountAccessibleName(account, $omitEmojiInDisplayNames)} followed you, @${account.acct}` | ||||||
|       ) |       ) | ||||||
|  |     }, | ||||||
|  |     methods: { | ||||||
|  |       openAuthorProfile () { | ||||||
|  |         let { accountId } = this.get() | ||||||
|  |         goto(`/accounts/${accountId}`) | ||||||
|  |       }, | ||||||
|  |       async mentionAuthor () { | ||||||
|  |         let { account } = this.get() | ||||||
|  |         await composeNewStatusMentioning(account) | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -36,7 +36,9 @@ | ||||||
|   {/if} |   {/if} | ||||||
| </article> | </article> | ||||||
| {#if shortcutScope} | {#if shortcutScope} | ||||||
| <Shortcut scope="{shortcutScope}" key="o" on:pressed="open()"/> |   <Shortcut scope={shortcutScope} key="o" on:pressed="open()" /> | ||||||
|  |   <Shortcut scope={shortcutScope} key="p" on:pressed="openAuthorProfile()" /> | ||||||
|  |   <Shortcut scope={shortcutScope} key="m" on:pressed="mentionAuthor()" /> | ||||||
| {/if} | {/if} | ||||||
| 
 | 
 | ||||||
| <style> | <style> | ||||||
|  | @ -127,6 +129,7 @@ | ||||||
|   import { measureText } from '../../_utils/measureText' |   import { measureText } from '../../_utils/measureText' | ||||||
|   import { LONG_POST_LENGTH, LONG_POST_TEXT } from '../../_static/statuses' |   import { LONG_POST_LENGTH, LONG_POST_TEXT } from '../../_static/statuses' | ||||||
|   import { absoluteDateFormatter } from '../../_utils/formatters' |   import { absoluteDateFormatter } from '../../_utils/formatters' | ||||||
|  |   import { composeNewStatusMentioning } from '../../_actions/mention' | ||||||
| 
 | 
 | ||||||
|   const INPUT_TAGS = new Set(['a', 'button', 'input', 'textarea']) |   const INPUT_TAGS = new Set(['a', 'button', 'input', 'textarea']) | ||||||
|   const isUserInputElement = node => INPUT_TAGS.has(node.localName) |   const isUserInputElement = node => INPUT_TAGS.has(node.localName) | ||||||
|  | @ -165,6 +168,7 @@ | ||||||
|       Shortcut |       Shortcut | ||||||
|     }, |     }, | ||||||
|     data: () => ({ |     data: () => ({ | ||||||
|  |       active: false, | ||||||
|       notification: void 0, |       notification: void 0, | ||||||
|       replyVisibility: void 0, |       replyVisibility: void 0, | ||||||
|       contentPreloaded: false, |       contentPreloaded: false, | ||||||
|  | @ -197,6 +201,14 @@ | ||||||
|       open () { |       open () { | ||||||
|         let { originalStatusId } = this.get() |         let { originalStatusId } = this.get() | ||||||
|         goto(`/statuses/${originalStatusId}`) |         goto(`/statuses/${originalStatusId}`) | ||||||
|  |       }, | ||||||
|  |       openAuthorProfile () { | ||||||
|  |         let { originalAccountId } = this.get() | ||||||
|  |         goto(`/accounts/${originalAccountId}`) | ||||||
|  |       }, | ||||||
|  |       async mentionAuthor () { | ||||||
|  |         let { originalAccount } = this.get() | ||||||
|  |         await composeNewStatusMentioning(originalAccount) | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     computed: { |     computed: { | ||||||
|  |  | ||||||
|  | @ -1,12 +1,14 @@ | ||||||
| import { Selector as $ } from 'testcafe' | import { Selector as $ } from 'testcafe' | ||||||
| import { | import { | ||||||
|  |   closeDialogButton, | ||||||
|  |   composeModalInput, | ||||||
|   getNthFavorited, |   getNthFavorited, | ||||||
|   getNthStatus, |   getNthStatus, | ||||||
|   getNthStatusContent, |   getNthStatusContent, | ||||||
|   getNthStatusMedia, |   getNthStatusMedia, | ||||||
|   getNthStatusSensitiveMediaButton, |   getNthStatusSensitiveMediaButton, | ||||||
|   getNthStatusSpoiler, |   getNthStatusSpoiler, | ||||||
|   getUrl, notificationsNavButton, |   getUrl, modalDialog, | ||||||
|   scrollToStatus |   scrollToStatus | ||||||
| } from '../utils' | } from '../utils' | ||||||
| import { homeTimeline } from '../fixtures' | import { homeTimeline } from '../fixtures' | ||||||
|  | @ -128,19 +130,28 @@ test('Shortcut f toggles favorite status', async t => { | ||||||
|     .expect(getNthFavorited(idx)).eql('false') |     .expect(getNthFavorited(idx)).eql('false') | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| test('Shortcut f toggles favorite status in notification', async t => { | test('Shortcut p toggles profile', async t => { | ||||||
|   let idx = 0 |   let idx = indexWhere(homeTimeline, _ => _.content === 'pinned toot 1') | ||||||
|   await loginAsFoobar(t) |   await loginAsFoobar(t) | ||||||
|   await t |   await t | ||||||
|     .expect(getUrl()).eql('http://localhost:4002/') |     .expect(getUrl()).eql('http://localhost:4002/') | ||||||
|     .click(notificationsNavButton) |  | ||||||
|     .expect(getUrl()).contains('/notifications') |  | ||||||
|     .expect(getNthStatus(idx).exists).ok({ timeout: 30000 }) |     .expect(getNthStatus(idx).exists).ok({ timeout: 30000 }) | ||||||
|     .expect(getNthFavorited(idx)).eql('false') |  | ||||||
|     .pressKey('j '.repeat(idx + 1)) |     .pressKey('j '.repeat(idx + 1)) | ||||||
|     .expect(getNthStatus(idx).hasClass('status-active')).ok() |     .expect(getNthStatus(idx).hasClass('status-active')).ok() | ||||||
|     .pressKey('f') |     .pressKey('p') | ||||||
|     .expect(getNthFavorited(idx)).eql('true') |     .expect(getUrl()).contains('/accounts/3') | ||||||
|     .pressKey('f') | }) | ||||||
|     .expect(getNthFavorited(idx)).eql('false') | 
 | ||||||
|  | test('Shortcut m toggles mention', async t => { | ||||||
|  |   let idx = indexWhere(homeTimeline, _ => _.content === 'pinned toot 1') | ||||||
|  |   await loginAsFoobar(t) | ||||||
|  |   await t | ||||||
|  |     .expect(getUrl()).eql('http://localhost:4002/') | ||||||
|  |     .expect(getNthStatus(idx).exists).ok({ timeout: 30000 }) | ||||||
|  |     .pressKey('j '.repeat(idx + 1)) | ||||||
|  |     .expect(getNthStatus(idx).hasClass('status-active')).ok() | ||||||
|  |     .pressKey('m') | ||||||
|  |     .expect(composeModalInput.value).eql('@quux ') | ||||||
|  |     .click(closeDialogButton) | ||||||
|  |     .expect(modalDialog.exists).notOk() | ||||||
| }) | }) | ||||||
|  |  | ||||||
							
								
								
									
										58
									
								
								tests/spec/026-shortcuts-notification.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								tests/spec/026-shortcuts-notification.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | ||||||
|  | import { | ||||||
|  |   closeDialogButton, | ||||||
|  |   composeModalInput, | ||||||
|  |   getNthFavorited, | ||||||
|  |   getNthStatus, | ||||||
|  |   getUrl, modalDialog, notificationsNavButton | ||||||
|  | } from '../utils' | ||||||
|  | import { loginAsFoobar } from '../roles' | ||||||
|  | 
 | ||||||
|  | fixture`026-shortcuts-notification.js` | ||||||
|  |   .page`http://localhost:4002` | ||||||
|  | 
 | ||||||
|  | test('Shortcut f toggles favorite status in notification', async t => { | ||||||
|  |   let idx = 0 | ||||||
|  |   await loginAsFoobar(t) | ||||||
|  |   await t | ||||||
|  |     .expect(getUrl()).eql('http://localhost:4002/') | ||||||
|  |     .click(notificationsNavButton) | ||||||
|  |     .expect(getUrl()).contains('/notifications') | ||||||
|  |     .expect(getNthStatus(idx).exists).ok({ timeout: 30000 }) | ||||||
|  |     .expect(getNthFavorited(idx)).eql('false') | ||||||
|  |     .pressKey('j '.repeat(idx + 1)) | ||||||
|  |     .expect(getNthStatus(idx).hasClass('status-active')).ok() | ||||||
|  |     .pressKey('f') | ||||||
|  |     .expect(getNthFavorited(idx)).eql('true') | ||||||
|  |     .pressKey('f') | ||||||
|  |     .expect(getNthFavorited(idx)).eql('false') | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | test('Shortcut p toggles profile in a follow notification', async t => { | ||||||
|  |   let idx = 5 // "@quux followed you"
 | ||||||
|  |   await loginAsFoobar(t) | ||||||
|  |   await t | ||||||
|  |     .expect(getUrl()).eql('http://localhost:4002/') | ||||||
|  |     .click(notificationsNavButton) | ||||||
|  |     .expect(getUrl()).contains('/notifications') | ||||||
|  |     .expect(getNthStatus(0).exists).ok({ timeout: 30000 }) | ||||||
|  |     .pressKey('j '.repeat(idx + 1)) | ||||||
|  |     .expect(getNthStatus(idx).hasClass('status-active')).ok() | ||||||
|  |     .pressKey('p') | ||||||
|  |     .expect(getUrl()).contains('/accounts/3') | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | test('Shortcut m toggles mention in a follow notification', async t => { | ||||||
|  |   let idx = 5 // "@quux followed you"
 | ||||||
|  |   await loginAsFoobar(t) | ||||||
|  |   await t | ||||||
|  |     .expect(getUrl()).eql('http://localhost:4002/') | ||||||
|  |     .click(notificationsNavButton) | ||||||
|  |     .expect(getUrl()).contains('/notifications') | ||||||
|  |     .expect(getNthStatus(0).exists).ok({ timeout: 30000 }) | ||||||
|  |     .pressKey('j '.repeat(idx + 1)) | ||||||
|  |     .expect(getNthStatus(idx).hasClass('status-active')).ok() | ||||||
|  |     .pressKey('m') | ||||||
|  |     .expect(composeModalInput.value).eql('@quux ') | ||||||
|  |     .click(closeDialogButton) | ||||||
|  |     .expect(modalDialog.exists).notOk() | ||||||
|  | }) | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue