left-sidebar.spec.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import {
  3. fireEvent,
  4. getAllByRole,
  5. getByLabelText,
  6. getByRole,
  7. } from '@testing-library/vue'
  8. import { visitView } from '#tests/support/components/visitView.ts'
  9. import { mockPermissions } from '#tests/support/mock-permissions.ts'
  10. import { mockUserCurrent } from '#tests/support/mock-userCurrent.ts'
  11. import { mockLogoutMutation } from '#shared/graphql/mutations/logout.mocks.ts'
  12. describe('Left sidebar', () => {
  13. beforeEach(() => {
  14. mockUserCurrent({
  15. id: 'gid://zammad/User/999',
  16. firstname: 'Nicole',
  17. lastname: 'Braun',
  18. fullname: 'Nicole Braun',
  19. preferences: {},
  20. })
  21. })
  22. afterEach(() => {
  23. localStorage.clear()
  24. })
  25. describe('width handling', () => {
  26. it('renders initially with the default width', async () => {
  27. const view = await visitView('/')
  28. const aside = view.getByRole('complementary')
  29. expect(aside.parentElement).toHaveStyle({
  30. gridTemplateColumns: '260px 1fr',
  31. })
  32. })
  33. it('restores stored width', async () => {
  34. localStorage.setItem('gid://zammad/User/999-left-sidebar-width', '216')
  35. const view = await visitView('/')
  36. const aside = view.getByRole('complementary')
  37. expect(aside.parentElement).toHaveStyle({
  38. gridTemplateColumns: '216px 1fr',
  39. })
  40. })
  41. it('supports collapsing/expanding', async () => {
  42. const view = await visitView('/')
  43. const aside = view.getByRole('complementary')
  44. const collapseButton = getByRole(aside, 'button', {
  45. name: 'Collapse sidebar',
  46. })
  47. await view.events.click(collapseButton)
  48. expect(aside.parentElement).toHaveStyle({
  49. gridTemplateColumns: '56px 1fr',
  50. })
  51. const expandButton = getByRole(aside, 'button', {
  52. name: 'Expand sidebar',
  53. })
  54. await view.events.click(expandButton)
  55. expect(aside.parentElement).toHaveStyle({
  56. gridTemplateColumns: '260px 1fr',
  57. })
  58. })
  59. it('restores collapsed state width', async () => {
  60. localStorage.setItem(
  61. 'gid://zammad/User/999-left-sidebar-collapsed',
  62. 'true',
  63. )
  64. const view = await visitView('/')
  65. const aside = view.getByRole('complementary')
  66. expect(aside.parentElement).toHaveStyle({
  67. gridTemplateColumns: '56px 1fr',
  68. })
  69. })
  70. it('supports resizing', async () => {
  71. const view = await visitView('/')
  72. const aside = view.getByRole('complementary')
  73. const resizeHandle = getByLabelText(aside, 'Resize sidebar')
  74. await fireEvent.mouseDown(resizeHandle, { clientX: 260 })
  75. await fireEvent.mouseMove(document, { clientX: 216 })
  76. await fireEvent.mouseUp(document, { clientX: 216 })
  77. expect(aside.parentElement).toHaveStyle({
  78. gridTemplateColumns: '216px 1fr',
  79. })
  80. })
  81. it('supports resetting', async () => {
  82. localStorage.setItem('gid://zammad/User/999-left-sidebar-width', '216')
  83. const view = await visitView('/')
  84. const aside = view.getByRole('complementary')
  85. const resizeHandle = getByLabelText(aside, 'Resize sidebar')
  86. await view.events.dblClick(resizeHandle)
  87. expect(aside.parentElement).toHaveStyle({
  88. gridTemplateColumns: '260px 1fr',
  89. })
  90. })
  91. })
  92. describe('User menu', () => {
  93. afterEach(() => {
  94. vi.clearAllMocks()
  95. })
  96. it.each([{ collapsed: false }, { collapsed: true }])(
  97. 'shows menu popover on click (collapsed: $collapsed)',
  98. async ({ collapsed }) => {
  99. localStorage.setItem(
  100. 'gid://zammad/User/999-left-sidebar-collapsed',
  101. String(collapsed),
  102. )
  103. const view = await visitView('/')
  104. const aside = view.getByRole('complementary')
  105. const avatarButton = getByRole(aside, 'button', {
  106. name: 'Nicole Braun',
  107. })
  108. expect(avatarButton).toHaveTextContent('NB')
  109. await view.events.click(avatarButton)
  110. const popover = view.getByRole('region', { name: 'Nicole Braun' })
  111. expect(popover).toHaveTextContent('Nicole Braun')
  112. const menu = getByRole(popover, 'menu')
  113. const menuItems = getAllByRole(menu, 'menuitem')
  114. expect(menuItems).toHaveLength(4)
  115. },
  116. )
  117. it('supports cycling appearance state', async () => {
  118. mockPermissions(['user_preferences.appearance'])
  119. const view = await visitView('/')
  120. const aside = view.getByRole('complementary')
  121. const avatarButton = getByRole(aside, 'button', { name: 'Nicole Braun' })
  122. await view.events.click(avatarButton)
  123. const appearanceButton = view.getByRole('button', { name: 'Appearance' })
  124. const appearanceSwitch = view.getByRole('checkbox', { name: 'Dark Mode' })
  125. expect(appearanceSwitch).toBePartiallyChecked()
  126. await view.events.click(appearanceSwitch)
  127. expect(appearanceSwitch).toBeChecked()
  128. await view.events.click(appearanceButton)
  129. expect(appearanceSwitch).not.toBeChecked()
  130. await view.events.click(appearanceSwitch)
  131. expect(appearanceSwitch).toBePartiallyChecked()
  132. })
  133. it('supports navigating to playground', async () => {
  134. const view = await visitView('/')
  135. const aside = view.getByRole('complementary')
  136. const avatarButton = getByRole(aside, 'button', { name: 'Nicole Braun' })
  137. await view.events.click(avatarButton)
  138. const playgroundLink = view.getByRole('link', {
  139. name: 'Playground',
  140. })
  141. await view.events.click(playgroundLink)
  142. await vi.waitFor(() => {
  143. expect(view, 'correctly redirects to playground page').toHaveCurrentUrl(
  144. '/playground',
  145. )
  146. })
  147. expect(
  148. view.queryByRole('region', { name: 'User menu' }),
  149. ).not.toBeInTheDocument()
  150. })
  151. // TODO: Cover keyboard shortcuts menu item when ready.
  152. it('supports navigating to personal settings', async () => {
  153. const view = await visitView('/')
  154. const aside = view.getByRole('complementary')
  155. const avatarButton = getByRole(aside, 'button', { name: 'Nicole Braun' })
  156. await view.events.click(avatarButton)
  157. const personalSettingsLink = view.getByRole('link', {
  158. name: 'Profile settings',
  159. })
  160. await view.events.click(personalSettingsLink)
  161. await vi.waitFor(() => {
  162. expect(
  163. view,
  164. 'correctly redirects to personal settings page',
  165. ).toHaveCurrentUrl('/personal-setting')
  166. })
  167. expect(
  168. view.queryByRole('region', { name: 'User menu' }),
  169. ).not.toBeInTheDocument()
  170. })
  171. it('supports signing out', async () => {
  172. const view = await visitView('/')
  173. const aside = view.getByRole('complementary')
  174. const avatarButton = getByRole(aside, 'button', { name: 'Nicole Braun' })
  175. await view.events.click(avatarButton)
  176. const logoutLink = view.getByRole('link', { name: 'Sign out' })
  177. mockLogoutMutation({
  178. logout: {
  179. success: true,
  180. externalLogoutUrl: null,
  181. },
  182. })
  183. await view.events.click(logoutLink)
  184. await vi.waitFor(() => {
  185. expect(view, 'correctly redirects to login page').toHaveCurrentUrl(
  186. '/login',
  187. )
  188. })
  189. expect(
  190. view.queryByRole('region', { name: 'User menu' }),
  191. ).not.toBeInTheDocument()
  192. })
  193. })
  194. })