app/frontend/apps/desktop/pages/personal-setting/__tests__/personal-setting-out-of-office.spec.ts
// 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.')
})
})
})