123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- import { getByRole } from '@testing-library/vue'
- import { storeToRefs } from 'pinia'
- import { type RouteRecordRaw } from 'vue-router'
- import {
- getAllByIconName,
- getByIconName,
- } from '#tests/support/components/iconQueries.ts'
- import { renderComponent } from '#tests/support/components/index.ts'
- import { getTestRouter } from '#tests/support/components/renderComponent.ts'
- import { mockApplicationConfig } from '#tests/support/mock-applicationConfig.ts'
- import { waitForNextTick } from '#tests/support/utils.ts'
- import {
- EnumTaskbarEntity,
- EnumTaskbarEntityAccess,
- EnumTicketStateColorCode,
- } from '#shared/graphql/types.ts'
- import { convertToGraphQLId } from '#shared/graphql/utils.ts'
- import { waitForUserCurrentTaskbarItemDeleteMutationCalls } from '#desktop/entities/user/current/graphql/mutations/userCurrentTaskbarItemDelete.mocks.ts'
- import {
- mockUserCurrentTaskbarItemListQuery,
- waitForUserCurrentTaskbarItemListQueryCalls,
- } from '#desktop/entities/user/current/graphql/queries/userCurrentTaskbarItemList.mocks.ts'
- import { getUserCurrentTaskbarItemListUpdatesSubscriptionHandler } from '#desktop/entities/user/current/graphql/subscriptions/userCurrentTaskbarItemListUpdates.mocks.ts'
- import { getUserCurrentTaskbarItemUpdatesSubscriptionHandler } from '#desktop/entities/user/current/graphql/subscriptions/userCurrentTaskbarItemUpdates.mocks.ts'
- import { useUserCurrentTaskbarTabsStore } from '#desktop/entities/user/current/stores/taskbarTabs.ts'
- import UserTaskbarTabs, { type Props } from '../UserTaskbarTabs.vue'
- import '#tests/graphql/builders/mocks.ts'
- const waitForVariantConfirmationMock = vi
- .fn()
- .mockImplementation((variant) => variant === 'unsaved')
- vi.mock('#shared/composables/useConfirmation.ts', async () => ({
- useConfirmation: () => ({
- waitForVariantConfirmation: waitForVariantConfirmationMock,
- }),
- }))
- const renderUserTaskbarTabs = async (
- options: {
- props?: Partial<Props>
- routerRoutes?: RouteRecordRaw[]
- } = {},
- ) => {
- const wrapper = renderComponent(UserTaskbarTabs, {
- router: true,
- store: true,
- dialog: true,
- ...options,
- })
- await waitForUserCurrentTaskbarItemListQueryCalls()
- await waitForNextTick() // initial list render
- await waitForNextTick() // computed async for teleport content
- return wrapper
- }
- describe('UserTaskbarTabs.vue', () => {
- beforeAll(() => {
- mockApplicationConfig({
- ticket_hook: 'Ticket#',
- ui_task_mananger_max_task_count: 30,
- })
- })
- it.each([{ collapsed: false }, { collapsed: true }])(
- 'does not render anything in case taskbar is empty (collapsed: $collapsed)',
- async ({ collapsed }) => {
- mockUserCurrentTaskbarItemListQuery({
- userCurrentTaskbarItemList: [],
- })
- const wrapper = await renderUserTaskbarTabs({
- props: {
- collapsed,
- },
- })
- expect(wrapper.queryByText('Tabs')).not.toBeInTheDocument()
- expect(
- wrapper.queryByRole('button', {
- name: 'List of all user taskbar tabs',
- }),
- ).not.toBeInTheDocument()
- },
- )
- it('renders ticket tab', async () => {
- // Rely on the default ticket tab from the `UserTaskbarItem` factory.
- const wrapper = await renderUserTaskbarTabs({
- routerRoutes: [
- {
- path: '/',
- name: 'Main',
- component: { template: '<div></div>' },
- },
- {
- path: '/tickets/1',
- name: 'Test',
- component: { template: '<div></div>' },
- },
- ],
- })
- expect(wrapper.getByText('Tabs')).toBeInTheDocument()
- const tab = wrapper.getByRole('listitem')
- expect(getByIconName(tab, 'check-circle-no')).toBeInTheDocument()
- expect(tab).toHaveTextContent('Welcome to Zammad!')
- expect(tab).toHaveAccessibleDescription(
- 'Drag and drop to reorder your tabs.',
- )
- const link = getByRole(tab, 'link')
- expect(link).toHaveAttribute('href', '/desktop/tickets/1')
- expect(link).toHaveAccessibleName('Ticket#53001 - Welcome to Zammad!')
- expect(link).not.toHaveClass('!bg-yellow-500')
- // Test active background based on the open state.
- const router = getTestRouter()
- await router.push('/tickets/1')
- expect(link).toHaveClass('!bg-yellow-500')
- expect(
- getByRole(tab, 'button', { name: 'Close this tab' }),
- ).toBeInTheDocument()
- })
- it('renders ticket create tab', async () => {
- mockUserCurrentTaskbarItemListQuery({
- userCurrentTaskbarItemList: [
- {
- __typename: 'UserTaskbarItem',
- id: convertToGraphQLId('Taskbar', 1),
- key: 'TicketCreateScreen-999',
- callback: EnumTaskbarEntity.TicketCreate,
- entityAccess: EnumTaskbarEntityAccess.Granted,
- entity: {
- __typename: 'UserTaskbarItemEntityTicketCreate',
- uid: '999',
- title: 'Test title',
- createArticleTypeKey: 'phone-in',
- },
- },
- ],
- })
- const wrapper = await renderUserTaskbarTabs()
- expect(wrapper.getByText('Tabs')).toBeInTheDocument()
- const tab = wrapper.getByRole('listitem')
- expect(getByIconName(tab, 'pencil')).toBeInTheDocument()
- expect(tab).toHaveTextContent('Received Call: Test title')
- expect(tab).toHaveAccessibleDescription(
- 'Drag and drop to reorder your tabs.',
- )
- const link = getByRole(tab, 'link')
- expect(link).toHaveAttribute('href', '/desktop/tickets/create/999')
- expect(link).toHaveAccessibleName('Received Call: Test title')
- expect(
- getByRole(tab, 'button', { name: 'Close this tab' }),
- ).toBeInTheDocument()
- })
- it('renders forbidden tab', async () => {
- mockUserCurrentTaskbarItemListQuery({
- userCurrentTaskbarItemList: [
- {
- __typename: 'UserTaskbarItem',
- id: convertToGraphQLId('Taskbar', 999),
- key: 'Ticket-999',
- callback: EnumTaskbarEntity.TicketZoom,
- entityAccess: EnumTaskbarEntityAccess.Forbidden,
- entity: null,
- },
- ],
- })
- const wrapper = await renderUserTaskbarTabs()
- expect(wrapper.getByText('Tabs')).toBeInTheDocument()
- const tab = wrapper.getByRole('listitem')
- expect(getAllByIconName(tab, 'x-lg')[0]).toHaveClass('text-red-500')
- expect(tab).toHaveTextContent('Access denied')
- expect(tab).toHaveAccessibleDescription(
- 'Drag and drop to reorder your tabs.',
- )
- const link = getByRole(tab, 'link')
- expect(link).toHaveAttribute('href', '/desktop/tickets/999')
- expect(link).toHaveAccessibleName(
- 'You have insufficient rights to view this object.',
- )
- expect(
- getByRole(tab, 'button', { name: 'Close this tab' }),
- ).toBeInTheDocument()
- })
- it('renders not found tab', async () => {
- mockUserCurrentTaskbarItemListQuery({
- userCurrentTaskbarItemList: [
- {
- __typename: 'UserTaskbarItem',
- id: convertToGraphQLId('Taskbar', 999),
- key: 'Ticket-999',
- callback: EnumTaskbarEntity.TicketZoom,
- entityAccess: EnumTaskbarEntityAccess.NotFound,
- entity: null,
- },
- ],
- })
- const wrapper = await renderUserTaskbarTabs()
- expect(wrapper.getByText('Tabs')).toBeInTheDocument()
- const tab = wrapper.getByRole('listitem')
- expect(getAllByIconName(tab, 'x-lg')[0]).toHaveClass('text-red-500')
- expect(tab).toHaveTextContent('Not found')
- expect(tab).toHaveAccessibleDescription(
- 'Drag and drop to reorder your tabs.',
- )
- const link = getByRole(tab, 'link')
- expect(link).toHaveAttribute('href', '/desktop/tickets/999')
- expect(link).toHaveAccessibleName('This object could not be found.')
- expect(
- getByRole(tab, 'button', { name: 'Close this tab' }),
- ).toBeInTheDocument()
- })
- it('renders popover button in collapsed mode', async () => {
- // Rely on the default ticket tab from the `UserTaskbarItem` factory.
- const wrapper = await renderUserTaskbarTabs({
- props: {
- collapsed: true,
- },
- })
- expect(wrapper.queryByText('Tabs')).not.toBeInTheDocument()
- const popoverButton = wrapper.getByRole('button', {
- name: 'List of all user taskbar tabs',
- })
- expect(getByIconName(popoverButton, 'card-list')).toBeInTheDocument()
- await wrapper.events.click(popoverButton)
- const popover = wrapper.getByRole('region', {
- name: 'List of all user taskbar tabs',
- })
- const tab = getByRole(popover, 'listitem')
- expect(getByIconName(tab, 'check-circle-no')).toBeInTheDocument()
- expect(tab).toHaveTextContent('Welcome to Zammad!')
- expect(tab).not.toHaveAccessibleDescription(
- 'Drag and drop to reorder your tabs.',
- )
- const link = getByRole(tab, 'link')
- expect(link).toHaveAttribute('href', '/desktop/tickets/1')
- expect(link).toHaveAccessibleName('Ticket#53001 - Welcome to Zammad!')
- expect(link).toHaveClass('!bg-yellow-500')
- expect(
- getByRole(tab, 'button', { name: 'Close this tab' }),
- ).toBeInTheDocument()
- })
- it('implements tab updates subscription', async () => {
- mockUserCurrentTaskbarItemListQuery({
- userCurrentTaskbarItemList: [],
- })
- const wrapper = await renderUserTaskbarTabs()
- expect(wrapper.queryByText('Tabs')).not.toBeInTheDocument()
- // Add item.
- await getUserCurrentTaskbarItemUpdatesSubscriptionHandler().trigger({
- userCurrentTaskbarItemUpdates: {
- addItem: {
- __typename: 'UserTaskbarItem',
- id: convertToGraphQLId('Taskbar', 42),
- key: 'Ticket-42',
- callback: EnumTaskbarEntity.TicketZoom,
- entityAccess: EnumTaskbarEntityAccess.Granted,
- entity: {
- __typename: 'Ticket',
- id: convertToGraphQLId('Ticket', 42),
- internalId: 42,
- number: '53042',
- title: 'Test ticket title',
- stateColorCode: EnumTicketStateColorCode.Pending,
- state: {
- __typename: 'TicketState',
- name: 'pending reminder',
- },
- },
- },
- updateItem: null,
- removeItem: null,
- },
- })
- expect(wrapper.queryByText('Tabs')).toBeInTheDocument()
- expect(wrapper.getByText('Test ticket title')).toBeInTheDocument()
- // Update item.
- await getUserCurrentTaskbarItemUpdatesSubscriptionHandler().trigger({
- userCurrentTaskbarItemUpdates: {
- addItem: null,
- updateItem: {
- __typename: 'UserTaskbarItem',
- id: convertToGraphQLId('Taskbar', 42),
- key: 'Ticket-42',
- callback: EnumTaskbarEntity.TicketZoom,
- entityAccess: EnumTaskbarEntityAccess.Granted,
- entity: {
- __typename: 'Ticket',
- id: convertToGraphQLId('Ticket', 42),
- internalId: 42,
- number: '53042',
- title: 'New ticket title',
- stateColorCode: EnumTicketStateColorCode.Open,
- state: {
- __typename: 'TicketState',
- name: 'open',
- },
- },
- },
- removeItem: null,
- },
- })
- expect(wrapper.queryByText('Test ticket title')).not.toBeInTheDocument()
- expect(wrapper.getByText('New ticket title')).toBeInTheDocument()
- // Remove item.
- await getUserCurrentTaskbarItemUpdatesSubscriptionHandler().trigger({
- userCurrentTaskbarItemUpdates: {
- addItem: null,
- updateItem: null,
- removeItem: convertToGraphQLId('Taskbar', 42),
- },
- })
- expect(wrapper.queryByText('New ticket title')).not.toBeInTheDocument()
- })
- it('supports updating the tabs after they got reordered', async () => {
- mockUserCurrentTaskbarItemListQuery({
- userCurrentTaskbarItemList: [
- {
- __typename: 'UserTaskbarItem',
- id: convertToGraphQLId('Taskbar', 1),
- key: 'TicketCreateScreen-999',
- callback: EnumTaskbarEntity.TicketCreate,
- entityAccess: EnumTaskbarEntityAccess.Granted,
- entity: {
- __typename: 'UserTaskbarItemEntityTicketCreate',
- uid: '999',
- title: 'First ticket',
- createArticleTypeKey: 'phone-in',
- },
- prio: 1,
- formId: 'foo',
- changed: false,
- dirty: false,
- notify: false,
- updatedAt: '2024-07-24T15:42:28.212Z',
- },
- {
- __typename: 'UserTaskbarItem',
- id: convertToGraphQLId('Taskbar', 2),
- key: 'Ticket-42',
- callback: EnumTaskbarEntity.TicketZoom,
- entityAccess: EnumTaskbarEntityAccess.Granted,
- entity: {
- __typename: 'Ticket',
- id: convertToGraphQLId('Ticket', 42),
- internalId: 42,
- number: '53042',
- title: 'Second ticket',
- stateColorCode: EnumTicketStateColorCode.Pending,
- state: {
- __typename: 'TicketState',
- name: 'pending reminder',
- },
- },
- prio: 2,
- formId: 'bar',
- changed: false,
- dirty: false,
- notify: false,
- updatedAt: '2024-07-24T15:42:28.212Z',
- },
- ],
- })
- const wrapper = await renderUserTaskbarTabs()
- expect(wrapper.getByText('Tabs')).toBeInTheDocument()
- let tabs = wrapper.getAllByRole('listitem')
- expect(tabs).toHaveLength(2)
- expect(tabs[0]).toHaveTextContent('First ticket')
- expect(tabs[1]).toHaveTextContent('Second ticket')
- // Reorder tabs.
- await getUserCurrentTaskbarItemListUpdatesSubscriptionHandler().trigger({
- userCurrentTaskbarItemListUpdates: {
- __typename: 'UserCurrentTaskbarItemListUpdatesPayload',
- taskbarItemList: [
- {
- __typename: 'UserTaskbarItem',
- id: convertToGraphQLId('Taskbar', 2),
- prio: 1,
- },
- {
- __typename: 'UserTaskbarItem',
- id: convertToGraphQLId('Taskbar', 1),
- prio: 2,
- },
- ],
- },
- })
- tabs = wrapper.getAllByRole('listitem')
- expect(tabs).toHaveLength(2)
- expect(tabs[0]).toHaveTextContent('Second ticket')
- expect(tabs[1]).toHaveTextContent('First ticket')
- })
- it('supports closing tabs', async () => {
- // Rely on the default ticket tab from the `UserTaskbarItem` factory.
- const wrapper = await renderUserTaskbarTabs()
- expect(wrapper.getByText('Tabs')).toBeInTheDocument()
- const tab = wrapper.getByRole('listitem')
- await wrapper.events.click(
- getByRole(tab, 'button', { name: 'Close this tab' }),
- )
- const calls = await waitForUserCurrentTaskbarItemDeleteMutationCalls()
- expect(waitForVariantConfirmationMock).toHaveBeenCalled()
- expect(calls.at(-1)?.variables).toEqual({
- id: convertToGraphQLId('Taskbar', 1),
- })
- expect(tab).not.toBeInTheDocument()
- })
- it('supports redirecting to previous route when the current tab is closed', async () => {
- mockUserCurrentTaskbarItemListQuery({
- userCurrentTaskbarItemList: [
- {
- __typename: 'UserTaskbarItem',
- id: convertToGraphQLId('Taskbar', 1),
- key: 'TicketCreateScreen-999',
- callback: EnumTaskbarEntity.TicketCreate,
- entityAccess: EnumTaskbarEntityAccess.Granted,
- entity: {
- __typename: 'UserTaskbarItemEntityTicketCreate',
- uid: '999',
- title: 'First ticket',
- createArticleTypeKey: 'phone-in',
- },
- prio: 1,
- formId: 'foo',
- changed: false,
- dirty: false,
- notify: false,
- updatedAt: '2024-07-24T15:42:28.212Z',
- },
- {
- __typename: 'UserTaskbarItem',
- id: convertToGraphQLId('Taskbar', 2),
- key: 'Ticket-42',
- callback: EnumTaskbarEntity.TicketZoom,
- entityAccess: EnumTaskbarEntityAccess.Granted,
- entity: {
- __typename: 'Ticket',
- id: convertToGraphQLId('Ticket', 42),
- internalId: 42,
- number: '53042',
- title: 'Second ticket',
- stateColorCode: EnumTicketStateColorCode.Pending,
- state: {
- __typename: 'TicketState',
- name: 'pending reminder',
- },
- },
- prio: 2,
- formId: 'bar',
- changed: false,
- dirty: false,
- notify: false,
- updatedAt: '2024-07-24T15:42:28.212Z',
- },
- ],
- })
- const wrapper = await renderUserTaskbarTabs()
- // Simulate currently active tab by changing the store state directly.
- // Normally, this would be set by the route navigation guard (`activeTaskbarTab`).
- const { activeTaskbarTabEntityKey } = storeToRefs(
- useUserCurrentTaskbarTabsStore(),
- )
- activeTaskbarTabEntityKey.value = 'TicketCreateScreen-999'
- // Simulate the history stack by visiting different routes before the current one.
- const router = getTestRouter()
- await router.push('/tickets/42')
- await router.push('/tickets/create/999')
- const tabs = wrapper.getAllByRole('listitem')
- expect(tabs).toHaveLength(2)
- await wrapper.events.click(
- getByRole(tabs[0], 'button', { name: 'Close this tab' }),
- )
- await waitForUserCurrentTaskbarItemDeleteMutationCalls()
- expect(tabs[0]).not.toBeInTheDocument()
- await vi.waitFor(() => {
- expect(
- wrapper,
- 'correctly redirects to the previous route',
- ).toHaveCurrentUrl('/tickets/42')
- })
- })
- })
|