zammad/zammad

View on GitHub
app/frontend/apps/desktop/pages/dashboard/__tests__/left-sidebar.spec.ts

Summary

Maintainability
D
2 days
Test Coverage
// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

import {
  fireEvent,
  getAllByRole,
  getByLabelText,
  getByRole,
} from '@testing-library/vue'

import { visitView } from '#tests/support/components/visitView.ts'
import { mockPermissions } from '#tests/support/mock-permissions.ts'
import { mockUserCurrent } from '#tests/support/mock-userCurrent.ts'

import { mockLogoutMutation } from '#shared/graphql/mutations/logout.mocks.ts'

describe('Left sidebar', () => {
  beforeEach(() => {
    mockUserCurrent({
      id: 'gid://zammad/User/999',
      firstname: 'Nicole',
      lastname: 'Braun',
      fullname: 'Nicole Braun',
      preferences: {},
    })
  })

  afterEach(() => {
    localStorage.clear()
  })

  describe('width handling', () => {
    it('renders initially with the default width', async () => {
      const view = await visitView('/')

      const aside = view.getByRole('complementary')

      expect(aside.parentElement).toHaveStyle({
        gridTemplateColumns: '260px 1fr',
      })
    })

    it('restores stored width', async () => {
      localStorage.setItem('gid://zammad/User/999-left-sidebar-width', '216')

      const view = await visitView('/')

      const aside = view.getByRole('complementary')

      expect(aside.parentElement).toHaveStyle({
        gridTemplateColumns: '216px 1fr',
      })
    })

    it('supports collapsing/expanding', async () => {
      const view = await visitView('/')

      const aside = view.getByRole('complementary')
      const collapseButton = getByRole(aside, 'button', {
        name: 'Collapse this element',
      })

      await view.events.click(collapseButton)

      expect(aside.parentElement).toHaveStyle({
        gridTemplateColumns: '48px 1fr',
      })

      const expandButton = getByRole(aside, 'button', {
        name: 'Expand this element',
      })

      await view.events.click(expandButton)

      expect(aside.parentElement).toHaveStyle({
        gridTemplateColumns: '260px 1fr',
      })
    })

    it('restores collapsed state width', async () => {
      localStorage.setItem(
        'gid://zammad/User/999-left-sidebar-collapsed',
        'true',
      )

      const view = await visitView('/')

      const aside = view.getByRole('complementary')

      expect(aside.parentElement).toHaveStyle({
        gridTemplateColumns: '48px 1fr',
      })
    })

    it('supports resizing', async () => {
      const view = await visitView('/')

      const aside = view.getByRole('complementary')
      const resizeHandle = getByLabelText(aside, 'Resize sidebar')

      await fireEvent.mouseDown(resizeHandle, { clientX: 260 })
      await fireEvent.mouseMove(document, { clientX: 216 })
      await fireEvent.mouseUp(document, { clientX: 216 })

      expect(aside.parentElement).toHaveStyle({
        gridTemplateColumns: '216px 1fr',
      })
    })

    it('supports resetting', async () => {
      localStorage.setItem('gid://zammad/User/999-left-sidebar-width', '216')

      const view = await visitView('/')

      const aside = view.getByRole('complementary')
      const resizeHandle = getByLabelText(aside, 'Resize sidebar')

      await view.events.dblClick(resizeHandle)

      expect(aside.parentElement).toHaveStyle({
        gridTemplateColumns: '260px 1fr',
      })
    })
  })

  describe('User menu', () => {
    afterEach(() => {
      vi.clearAllMocks()
    })

    it.each([{ collapsed: false }, { collapsed: true }])(
      'shows menu popover on click (collapsed: $collapsed)',
      async ({ collapsed }) => {
        localStorage.setItem(
          'gid://zammad/User/999-left-sidebar-collapsed',
          String(collapsed),
        )

        const view = await visitView('/')

        const aside = view.getByRole('complementary')
        const avatarButton = getByRole(aside, 'button', { name: 'User menu' })

        expect(avatarButton).toHaveTextContent('NB')

        await view.events.click(avatarButton)

        const popover = view.getByRole('region', { name: 'User menu' })

        expect(popover).toHaveTextContent('Nicole Braun')

        const menu = getByRole(popover, 'menu')
        const menuItems = getAllByRole(menu, 'menuitem')

        expect(menuItems).toHaveLength(4)
      },
    )

    it('supports cycling appearance state', async () => {
      mockPermissions(['user_preferences.appearance'])

      const view = await visitView('/')

      const aside = view.getByRole('complementary')
      const avatarButton = getByRole(aside, 'button', { name: 'User menu' })

      await view.events.click(avatarButton)

      const appearanceButton = view.getByRole('button', { name: 'Appearance' })
      const appearanceSwitch = view.getByRole('checkbox', { name: 'Dark Mode' })

      expect(appearanceSwitch).toBePartiallyChecked()

      await view.events.click(appearanceSwitch)

      expect(appearanceSwitch).toBeChecked()

      await view.events.click(appearanceButton)

      expect(appearanceSwitch).not.toBeChecked()

      await view.events.click(appearanceSwitch)

      expect(appearanceSwitch).toBePartiallyChecked()
    })

    it('supports navigating to playground', async () => {
      const view = await visitView('/')

      const aside = view.getByRole('complementary')
      const avatarButton = getByRole(aside, 'button', { name: 'User menu' })

      await view.events.click(avatarButton)

      const playgroundLink = view.getByRole('link', {
        name: 'Playground',
      })

      await view.events.click(playgroundLink)

      await vi.waitFor(() => {
        expect(view, 'correctly redirects to playground page').toHaveCurrentUrl(
          '/playground',
        )
      })

      expect(
        view.queryByRole('region', { name: 'User menu' }),
      ).not.toBeInTheDocument()
    })

    // TODO: Cover keyboard shortcuts menu item when ready.

    it('supports navigating to personal settings', async () => {
      const view = await visitView('/')

      const aside = view.getByRole('complementary')
      const avatarButton = getByRole(aside, 'button', { name: 'User menu' })

      await view.events.click(avatarButton)

      const personalSettingsLink = view.getByRole('link', {
        name: 'Profile settings',
      })

      await view.events.click(personalSettingsLink)

      await vi.waitFor(() => {
        expect(
          view,
          'correctly redirects to personal settings page',
        ).toHaveCurrentUrl('/personal-setting')
      })

      expect(
        view.queryByRole('region', { name: 'User menu' }),
      ).not.toBeInTheDocument()
    })

    it('supports signing out', async () => {
      const view = await visitView('/')

      const aside = view.getByRole('complementary')
      const avatarButton = getByRole(aside, 'button', { name: 'User menu' })

      await view.events.click(avatarButton)

      const logoutLink = view.getByRole('link', { name: 'Sign out' })

      mockLogoutMutation({
        logout: {
          success: true,
          externalLogoutUrl: null,
        },
      })

      await view.events.click(logoutLink)

      await vi.waitFor(() => {
        expect(view, 'correctly redirects to login page').toHaveCurrentUrl(
          '/login',
        )
      })

      expect(
        view.queryByRole('region', { name: 'User menu' }),
      ).not.toBeInTheDocument()
    })
  })
})