zammad/zammad

View on GitHub
app/frontend/apps/desktop/pages/personal-setting/__tests__/personal-setting-out-of-office.spec.ts

Summary

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

import { getByRole } from '@testing-library/vue'

import { visitView } from '#tests/support/components/visitView.ts'
import { mockUserCurrent } from '#tests/support/mock-userCurrent.ts'
import { nullableMock, waitForNextTick } from '#tests/support/utils.ts'

import {
  mockAutocompleteSearchAgentQuery,
  waitForAutocompleteSearchAgentQueryCalls,
} from '#shared/components/Form/fields/FieldAgent/graphql/queries/autocompleteSearch/agent.mocks.ts'
import type { AutocompleteSearchUserEntry } from '#shared/graphql/types.ts'
import { convertToGraphQLId } from '#shared/graphql/utils.ts'

import { waitForUserCurrentOutOfOfficeMutationCalls } from '../graphql/mutations/userCurrentOutOfOffice.mocks.ts'

const agentAutocompleteOptions = [
  {
    __typename: 'AutocompleteSearchUserEntry',
    value: 1,
    label: 'foo',
    disabled: false,
    user: nullableMock({
      id: convertToGraphQLId('User', 1),
      internalId: 1,
      fullname: 'sample 1',
    }),
  },
  {
    __typename: 'AutocompleteSearchUserEntry',
    value: 2,
    label: 'bar',
    disabled: false,
    user: nullableMock({
      id: convertToGraphQLId('User', 2),
      internalId: 2,
      fullname: 'sample 1',
    }),
  },
] as AutocompleteSearchUserEntry[]

describe('Out of Office page', () => {
  describe('when enabled', () => {
    beforeEach(() => {
      mockUserCurrent({
        id: '123',
        internalId: 1,
        firstname: 'John',
        lastname: 'Doe',
        outOfOffice: true,
        preferences: { out_of_office_text: 'OOF holiday' },
        outOfOfficeStartAt: '2024-03-01',
        outOfOfficeEndAt: '2024-04-01',
        outOfOfficeReplacement: {
          id: convertToGraphQLId('User', 256),
          internalId: 256,
          fullname: 'Example Agent',
        },
      })
    })

    it('loads current Out of Office settings', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      expect(view.getByLabelText('Reason for absence')).toHaveValue(
        'OOF holiday',
      )
      expect(view.getByLabelText('Start and end date')).toHaveValue(
        '2024-03-01 - 2024-04-01',
      )
      expect(view.getByLabelText('Replacement agent')).toHaveValue(
        'Example Agent',
      )
      expect(view.getByLabelText('Active')).toBeChecked()
    })

    it('loads data updated elsewhere', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      expect(view.getByLabelText('Reason for absence')).toHaveValue(
        'OOF holiday',
      )

      mockUserCurrent({
        firstname: 'John',
        lastname: 'Doe',
        outOfOffice: true,
        preferences: { out_of_office_text: '' },
        outOfOfficeStartAt: '2024-03-01',
        outOfOfficeEndAt: '2024-04-01',
        outOfOfficeReplacement: {
          id: convertToGraphQLId('User', 256),
          internalId: 256,
          fullname: 'Example Agent',
        },
      })

      await waitForNextTick()

      expect(view.getByLabelText('Reason for absence')).toHaveValue('')
    })

    it('does not reset form if unrelated data was updated', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      expect(view.getByLabelText('Reason for absence')).toHaveValue(
        'OOF holiday',
      )

      const input = view.getByLabelText('Reason for absence')
      await view.events.clear(input)
      await view.events.type(input, 'new label')

      mockUserCurrent({
        firstname: 'John II',
        lastname: 'Doe',
        outOfOffice: true,
        preferences: { out_of_office_text: 'OOF holiday' },
        outOfOfficeStartAt: '2024-03-01',
        outOfOfficeEndAt: '2024-04-01',
        outOfOfficeReplacement: {
          id: convertToGraphQLId('User', 256),
          internalId: 256,
          fullname: 'Example Agent',
        },
      })

      await waitForNextTick()

      expect(view.getByLabelText('Reason for absence')).toHaveValue('new label')
    })

    it('shows success notification', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      await view.events.click(view.getByText('Save Out of Office'))

      expect(
        view.getByText('Out of Office settings have been saved successfully'),
      ).toBeInTheDocument()
    })

    it('can clear label', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      const input = view.getByLabelText('Reason for absence')
      await view.events.clear(input)

      await view.events.click(view.getByText('Save Out of Office'))

      const calls = await waitForUserCurrentOutOfOfficeMutationCalls()

      expect(calls.at(-1)?.variables).toEqual(
        expect.objectContaining({
          input: expect.objectContaining({
            text: '',
          }),
        }),
      )
    })

    it('cannot set date range to blank', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      const input = view.getByLabelText('Start and end date')
      const button = getByRole(input.parentElement!, 'button')
      await view.events.click(button)

      await view.events.click(view.getByText('Save Out of Office'))

      expect(input).toBeDescribedBy('This field is required.')
    })

    it('cannot set replacement agent to blank', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      const input = view.getByLabelText('Replacement agent')
      const button = getByRole(input, 'button')
      await view.events.click(button)

      await view.events.click(view.getByText('Save Out of Office'))

      expect(input).toBeDescribedBy('This field is required.')
    })

    it('cannot set replacement agent to themselves', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      const inputAgent = view.getByLabelText('Replacement agent')

      await view.events.click(inputAgent)

      const filterElement = getByRole(inputAgent, 'searchbox')

      mockAutocompleteSearchAgentQuery({
        autocompleteSearchAgent: agentAutocompleteOptions,
      })

      await view.events.type(filterElement, '*')

      const calls = await waitForAutocompleteSearchAgentQueryCalls()

      expect(calls.at(-1)?.variables).toEqual({
        input: expect.objectContaining({
          exceptInternalId: 1,
        }),
      })
    })

    it('can disable Out of Office', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      const input = view.getByLabelText('Active')
      await view.events.click(input)

      await view.events.click(view.getByText('Save Out of Office'))

      const calls = await waitForUserCurrentOutOfOfficeMutationCalls()

      expect(calls.at(-1)?.variables).toEqual(
        expect.objectContaining({
          input: expect.objectContaining({
            enabled: false,
          }),
        }),
      )
    })

    it('can disable Out of Office and unset settings', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      const inputActivated = view.getByLabelText('Active')
      await view.events.click(inputActivated)

      const inputDate = view.getByLabelText('Start and end date')
      const buttonDate = getByRole(inputDate.parentElement!, 'button')
      await view.events.click(buttonDate)

      const inputLabel = view.getByLabelText('Reason for absence')
      await view.events.clear(inputLabel)

      const inputAgent = view.getByLabelText('Replacement agent')
      const buttonAgent = getByRole(inputAgent, 'button')
      await view.events.click(buttonAgent)

      await view.events.click(view.getByText('Save Out of Office'))

      const calls = await waitForUserCurrentOutOfOfficeMutationCalls()

      expect(calls.at(-1)?.variables).toEqual(
        expect.objectContaining({
          input: expect.objectContaining({
            enabled: false,
            text: '',
            startAt: undefined,
            endAt: undefined,
            replacementId: undefined,
          }),
        }),
      )
    })
  })

  describe('when disabled', () => {
    beforeEach(() => {
      mockUserCurrent({
        firstname: 'John',
        lastname: 'Doe',
        outOfOffice: false,
        outOfOfficeStartAt: '',
        outOfOfficeEndAt: '',
        outOfOfficeReplacement: null,
      })
    })

    it('loads current Out of Office settings', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      expect(view.getByLabelText('Reason for absence')).toHaveValue('')
      expect(view.getByLabelText('Start and end date')).toHaveValue('')
      expect(view.getByLabelText('Replacement agent')).toHaveValue('')
      expect(view.getByLabelText('Active')).not.toBeChecked()
    })

    it('can set date range', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      const input = view.getByLabelText('Start and end date')
      await view.events.type(input, '2024-01-02 - 2024-02-02')
      await view.events.keyboard('{Enter}')

      await view.events.click(view.getByText('Save Out of Office'))

      const calls = await waitForUserCurrentOutOfOfficeMutationCalls()

      expect(calls.at(-1)?.variables).toEqual(
        expect.objectContaining({
          input: expect.objectContaining({
            startAt: '2024-01-02',
            endAt: '2024-02-02',
          }),
        }),
      )
    })

    it('can set replacement agent', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      const inputAgent = view.getByLabelText('Replacement agent')

      await view.events.click(inputAgent)

      const filterElement = getByRole(inputAgent, 'searchbox')

      mockAutocompleteSearchAgentQuery({
        autocompleteSearchAgent: [agentAutocompleteOptions[0]],
      })

      await view.events.type(filterElement, agentAutocompleteOptions[0].label)

      await waitForAutocompleteSearchAgentQueryCalls()

      await view.events.click(view.getAllByRole('option')[0])

      await view.events.click(view.getByText('Save Out of Office'))

      const calls = await waitForUserCurrentOutOfOfficeMutationCalls()

      expect(calls.at(-1)?.variables).toEqual(
        expect.objectContaining({
          input: expect.objectContaining({
            replacementId: convertToGraphQLId(
              'User',
              agentAutocompleteOptions[0].value,
            ),
          }),
        }),
      )
    })

    it('can enable Out of Office with settings', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      const inputLabel = view.getByLabelText('Active')
      await view.events.click(inputLabel)

      const inputDate = view.getByLabelText('Start and end date')
      await view.events.type(inputDate, '2024-01-02 - 2024-02-02')
      await view.events.keyboard('{Enter}')

      const inputAgent = view.getByLabelText('Replacement agent')

      await view.events.click(inputAgent)

      const filterElement = getByRole(inputAgent, 'searchbox')

      mockAutocompleteSearchAgentQuery({
        autocompleteSearchAgent: [agentAutocompleteOptions[0]],
      })

      await view.events.type(filterElement, agentAutocompleteOptions[0].label)

      await waitForAutocompleteSearchAgentQueryCalls()

      await view.events.click(view.getAllByRole('option')[0])

      await view.events.click(view.getByText('Save Out of Office'))

      const calls = await waitForUserCurrentOutOfOfficeMutationCalls()

      expect(calls.at(-1)?.variables).toEqual(
        expect.objectContaining({
          input: expect.objectContaining({
            startAt: '2024-01-02',
            endAt: '2024-02-02',
            replacementId: convertToGraphQLId(
              'User',
              agentAutocompleteOptions[0].value,
            ),
            enabled: true,
          }),
        }),
      )
    })

    it('cannot enable Out of Office without date range and replacement agent', async () => {
      const view = await visitView('/personal-setting/out-of-office')

      const input = view.getByLabelText('Active')
      await view.events.click(input)

      await view.events.click(view.getByText('Save Out of Office'))

      const inputDate = view.getByLabelText('Start and end date')
      expect(inputDate).toBeDescribedBy('This field is required.')
    })
  })
})