// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
import { getNode } from '@formkit/core'
import { waitFor, within } from '@testing-library/vue'
import ticketCustomerObjectAttributes from '#tests/graphql/factories/fixtures/ticket-customer-object-attributes.ts'
import { visitView } from '#tests/support/components/visitView.ts'
import { mockApplicationConfig } from '#tests/support/mock-applicationConfig.ts'
import { mockPermissions } from '#tests/support/mock-permissions.ts'
import { waitForNextTick } from '#tests/support/utils.ts'
import { waitForFormUpdaterQueryCalls } from '#shared/components/Form/graphql/queries/formUpdater.mocks.ts'
import { mockObjectManagerFrontendAttributesQuery } from '#shared/entities/object-attributes/graphql/queries/objectManagerFrontendAttributes.mocks.ts'
import { waitForTicketCreateMutationCalls } from '#shared/entities/ticket/graphql/mutations/create.mocks.ts'
import { waitForTicketSharedDraftStartCreateMutationCalls } from '#shared/entities/ticket-shared-draft-start/graphql/mutations/ticketSharedDraftStartCreate.mocks.ts'
import { waitForTicketSharedDraftStartDeleteMutationCalls } from '#shared/entities/ticket-shared-draft-start/graphql/mutations/ticketSharedDraftStartDelete.mocks.ts'
import { waitForTicketSharedDraftStartUpdateMutationCalls } from '#shared/entities/ticket-shared-draft-start/graphql/mutations/ticketSharedDraftStartUpdate.mocks.ts'
import {
mockTicketSharedDraftStartListQuery,
waitForTicketSharedDraftStartListQueryCalls,
} from '#shared/entities/ticket-shared-draft-start/graphql/queries/ticketSharedDraftStartList.mocks.ts'
import {
mockTicketSharedDraftStartSingleQuery,
waitForTicketSharedDraftStartSingleQueryCalls,
} from '#shared/entities/ticket-shared-draft-start/graphql/queries/ticketSharedDraftStartSingle.mocks.ts'
import { getTicketSharedDraftStartUpdateByGroupSubscriptionHandler } from '#shared/entities/ticket-shared-draft-start/graphql/subscriptions/ticketSharedDraftStartUpdateByGroup.mocks.ts'
import {
convertToGraphQLId,
getIdFromGraphQLId,
} from '#shared/graphql/utils.ts'
import { handleMockFormUpdaterQuery } from '#desktop/pages/ticket/__tests__/support/ticket-create-helpers.ts'
vi.hoisted(() => {
vi.setSystemTime('2024-07-03T13:48:09Z')
})
describe('ticket create view - shared drafts sidebar', async () => {
describe('with agent permissions', async () => {
beforeEach(() => {
mockApplicationConfig({
ui_ticket_create_available_types: [
'phone-in',
'phone-out',
'email-out',
],
})
mockPermissions(['ticket.agent'])
handleMockFormUpdaterQuery()
})
it('supports creating shared drafts', async () => {
const view = await visitView('/ticket/create')
await view.events.type(
await view.findByLabelText('Text'),
'foobar
Signature here
',
)
const formUpdaterCalls = await waitForFormUpdaterQueryCalls()
await vi.waitUntil(() => formUpdaterCalls.length === 2)
mockTicketSharedDraftStartListQuery({
ticketSharedDraftStartList: [],
})
await view.events.click(view.getByLabelText('Group'))
await view.events.click(view.getByRole('option', { name: 'Users' }))
await waitForTicketSharedDraftStartListQueryCalls()
const aside = within(
view.getByRole('complementary', {
name: 'Content sidebar',
}),
)
await view.events.type(
aside.getByLabelText('Create a shared draft'),
'Test shared draft 1',
)
await getNode('sharedDraftTitle')?.settled
await view.events.click(
aside.getByRole('link', { name: 'Create Shared Draft' }),
)
const calls = await waitForTicketSharedDraftStartCreateMutationCalls()
expect(calls.at(-1)?.variables).toEqual({
name: 'Test shared draft 1',
input: expect.objectContaining({
groupId: convertToGraphQLId('Group', 1),
content: expect.objectContaining({
body: 'foobar',
}),
}),
})
expect(view.getByRole('alert')).toHaveTextContent(
'Shared draft has been created successfully.',
)
await getTicketSharedDraftStartUpdateByGroupSubscriptionHandler().trigger(
{
ticketSharedDraftStartUpdateByGroup: {
sharedDraftStarts: [
{
id: convertToGraphQLId('Ticket::SharedDraftStart', 1),
name: 'Test shared draft 1',
updatedAt: '2024-07-03T13:48:09Z',
updatedBy: {
fullname: 'Erika Mustermann',
},
},
],
},
},
)
await waitForNextTick()
expect(
aside.getByRole('link', { name: 'Test shared draft 1' }),
).toBeInTheDocument()
})
it('supports applying shared drafts', async () => {
const view = await visitView('/ticket/create')
const draftToMock = {
id: convertToGraphQLId('Ticket::SharedDraftStart', 1),
name: 'Test shared draft 1',
content: {
title: 'foobar',
customer_id: 'test@example.com',
body: 'body',
},
updatedAt: '2024-07-03T13:48:09Z',
updatedBy: {
fullname: 'Erika Mustermann',
},
}
mockTicketSharedDraftStartListQuery({
ticketSharedDraftStartList: [draftToMock],
})
await view.events.click(await view.findByLabelText('Group'))
await view.events.click(view.getByRole('option', { name: 'Users' }))
await waitForTicketSharedDraftStartListQueryCalls()
const aside = within(
view.getByRole('complementary', {
name: 'Content sidebar',
}),
)
mockTicketSharedDraftStartSingleQuery({
ticketSharedDraftStartSingle: draftToMock,
})
await view.events.click(
aside.getByRole('link', { name: draftToMock.name }),
)
await waitForTicketSharedDraftStartSingleQueryCalls()
const flyout = within(
view.getByRole('complementary', {
name: 'Preview Shared Draft',
}),
)
expect(
flyout.getByText(draftToMock.updatedBy.fullname),
).toBeInTheDocument()
expect(flyout.getByText('just now')).toBeInTheDocument()
expect(flyout.getByText(draftToMock.content.body)).toBeInTheDocument()
await view.events.click(flyout.getByRole('button', { name: 'Apply' }))
expect(
await view.findByRole('dialog', { name: 'Apply Draft' }),
).toBeInTheDocument()
const dialog = within(
view.getByRole('dialog', {
name: 'Apply Draft',
}),
)
handleMockFormUpdaterQuery({
title: { value: draftToMock.content.title },
customer_id: {
value: draftToMock.content.customer_id,
options: [{ value: draftToMock.content.customer_id }],
},
body: { value: draftToMock.content.body },
pending_time: { show: false },
shared_draft_id: { value: getIdFromGraphQLId(draftToMock.id) },
})
await view.events.click(
dialog.getByRole('button', { name: 'Overwrite Content' }),
)
await waitFor(() => {
expect(
view.queryByRole('dialog', {
name: 'Apply Draft',
}),
).not.toBeInTheDocument()
})
const formUpdaterCalls = await waitForFormUpdaterQueryCalls()
expect(formUpdaterCalls.at(-1)?.variables).toEqual(
expect.objectContaining({
meta: expect.objectContaining({
additionalData: expect.objectContaining({
sharedDraftId: draftToMock.id,
draftType: 'start',
}),
}),
}),
)
await waitForNextTick()
expect(view.getByLabelText('Title')).toHaveValue(
draftToMock.content.title,
)
await view.events.click(view.getByRole('button', { name: 'Create' }))
const ticketCreateCalls = await waitForTicketCreateMutationCalls()
expect(ticketCreateCalls.at(-1)?.variables).toEqual(
expect.objectContaining({
input: expect.objectContaining({
title: draftToMock.content.title,
sharedDraftId: draftToMock.id,
}),
}),
)
})
it('supports updating shared drafts', async () => {
const view = await visitView('/ticket/create')
mockTicketSharedDraftStartListQuery({
ticketSharedDraftStartList: [
{
id: convertToGraphQLId('Ticket::SharedDraftStart', 1),
name: 'Test shared draft 1',
updatedAt: '2024-07-03T13:48:09Z',
updatedBy: {
fullname: 'Erika Mustermann',
},
},
],
})
await view.events.click(await view.findByLabelText('Group'))
await view.events.click(view.getByRole('option', { name: 'Users' }))
await waitForTicketSharedDraftStartListQueryCalls()
const aside = within(
view.getByRole('complementary', {
name: 'Content sidebar',
}),
)
mockTicketSharedDraftStartSingleQuery({
ticketSharedDraftStartSingle: {
name: 'Test shared draft 1',
content: {
body: 'foobar',
},
updatedAt: '2024-07-03T13:48:09Z',
updatedBy: {
fullname: 'Erika Mustermann',
},
},
})
await view.events.click(
aside.getByRole('link', { name: 'Test shared draft 1' }),
)
await waitForTicketSharedDraftStartSingleQueryCalls()
const flyout = within(
view.getByRole('complementary', {
name: 'Preview Shared Draft',
}),
)
await view.events.click(flyout.getByRole('button', { name: 'Apply' }))
const dialog = within(
await view.findByRole('dialog', { name: 'Apply Draft' }),
)
handleMockFormUpdaterQuery({
shared_draft_id: {
value: 1,
},
body: {
value: 'foobar',
},
})
await view.events.click(
dialog.getByRole('button', { name: 'Overwrite Content' }),
)
await waitFor(() => {
expect(
view.queryByRole('complementary', {
name: 'Preview Shared Draft',
}),
).not.toBeInTheDocument()
})
await view.events.click(
aside.getByRole('button', { name: 'Update Shared Draft' }),
)
const calls = await waitForTicketSharedDraftStartUpdateMutationCalls()
expect(calls.at(-1)?.variables).toEqual({
sharedDraftId: convertToGraphQLId('Ticket::SharedDraftStart', 1),
input: expect.objectContaining({
content: expect.objectContaining({
body: 'foobar',
}),
}),
})
expect(view.getByRole('alert')).toHaveTextContent(
'Shared draft has been updated successfully.',
)
expect(
aside.getByRole('button', { name: 'Update Shared Draft' }),
).toBeInTheDocument()
})
it('supports deleting shared drafts', async () => {
const view = await visitView('/ticket/create')
mockTicketSharedDraftStartListQuery({
ticketSharedDraftStartList: [
{
id: convertToGraphQLId('Ticket::SharedDraftStart', 1),
name: 'Test shared draft 1',
updatedAt: '2024-07-03T13:48:09Z',
updatedBy: {
fullname: 'Erika Mustermann',
},
},
],
})
await view.events.click(await view.findByLabelText('Group'))
await view.events.click(view.getByRole('option', { name: 'Users' }))
await waitForTicketSharedDraftStartListQueryCalls()
const aside = within(
view.getByRole('complementary', {
name: 'Content sidebar',
}),
)
mockTicketSharedDraftStartSingleQuery({
ticketSharedDraftStartSingle: {
name: 'Test shared draft 1',
content: {
body: 'foobar',
},
updatedAt: '2024-07-03T13:48:09Z',
updatedBy: {
fullname: 'Erika Mustermann',
},
},
})
await view.events.click(
aside.getByRole('link', { name: 'Test shared draft 1' }),
)
await waitForTicketSharedDraftStartSingleQueryCalls()
const flyout = within(
view.getByRole('complementary', {
name: 'Preview Shared Draft',
}),
)
await view.events.click(flyout.getByRole('button', { name: 'Delete' }))
const dialog = within(
await view.findByRole('dialog', { name: 'Delete Object' }),
)
await view.events.click(
dialog.getByRole('button', { name: 'Delete Object' }),
)
const calls = await waitForTicketSharedDraftStartDeleteMutationCalls()
expect(calls.at(-1)?.variables).toEqual({
sharedDraftId: convertToGraphQLId('Ticket::SharedDraftStart', 1),
})
await waitFor(() => {
expect(
view.queryByRole('complementary', {
name: 'Preview Shared Draft',
}),
).not.toBeInTheDocument()
})
// FIXME: Check why returning an empty array triggers the following console error in test environment only.
// Cache data may be lost when replacing the ticketSharedDraftStartList field of a Query object.
await getTicketSharedDraftStartUpdateByGroupSubscriptionHandler().trigger(
{
ticketSharedDraftStartUpdateByGroup: {},
},
)
expect(
aside.queryByRole('link', { name: 'Test shared draft 1' }),
).not.toBeInTheDocument()
})
})
describe('with customer permission', () => {
beforeEach(() => {
mockApplicationConfig({
customer_ticket_create: true,
})
mockPermissions(['ticket.customer'])
// Mock frontend attributes for customer context.
// TODO: check if we can mock the query twice based on the variable?
mockObjectManagerFrontendAttributesQuery({
objectManagerFrontendAttributes: ticketCustomerObjectAttributes(),
})
handleMockFormUpdaterQuery()
})
it('does not show', async () => {
const view = await visitView('/ticket/create')
await view.events.click(await view.findByLabelText('Group'))
await view.events.click(view.getByRole('option', { name: 'Users' }))
await waitForFormUpdaterQueryCalls()
expect(
view.queryByRole('complementary', {
name: 'Content sidebar',
}),
).not.toBeInTheDocument()
})
})
})