Maintenance: Update graphqlcodegenerator monorepo (major)

Approved-by: Benjamin Scharf <bs@zammad.com>

Co-authored-by: Dominik Klein <dk@zammad.com>
This commit is contained in:
renovatebot
2026-06-30 19:24:33 +02:00
committed by Dominik Klein
parent 885fa533aa
commit 4366446b9b
104 changed files with 8015 additions and 7689 deletions

View File

@@ -18,6 +18,18 @@ const mockerPreset = {
},
}
const documents = ['app/frontend/shared/**/*.graphql', 'app/frontend/apps/**/*.graphql']
const scalars = {
BinaryString: 'string',
NonEmptyString: 'string',
FormId: 'string',
ISO8601Date: 'string',
ISO8601DateTime: 'string',
JSON: 'any',
UriHttpString: 'string',
}
/** @type {import('@graphql-codegen/cli').CodegenConfig} */
const config = {
overwrite: true,
@@ -27,28 +39,34 @@ const config = {
addDocBlocks: false,
},
generates: {
'./app/frontend/shared/graphql/types.ts': {
documents: [
'app/frontend/shared/**/*.graphql',
'app/frontend/apps/**/*.graphql',
],
'./app/frontend/shared/graphql/schema-types.ts': {
config: {
scalars: {
BinaryString: 'string',
NonEmptyString: 'string',
FormId: 'string',
ISO8601Date: 'string',
ISO8601DateTime: 'string',
UriHttpString: 'string',
},
scalars,
},
plugins: ['typescript', 'typescript-operations'],
plugins: ['typescript'],
},
'./app/frontend/shared/graphql/types.ts': {
documents,
config: {
scalars,
importSchemaTypesFrom: './app/frontend/shared/graphql/schema-types.ts',
namespacedImportName: 'Types',
// eslint-disable-next-line zammad/zammad-detect-translatable-string
maybeValue: 'T | null | undefined',
nonOptionalTypename: true,
skipTypeNameForRoot: true,
},
plugins: [
{
add: {
content: ["export * from './schema-types'", 'export type { Exact }'],
},
},
'typescript-operations',
],
},
'./app/frontend/': {
documents: [
'app/frontend/shared/**/*.graphql',
'app/frontend/apps/**/*.graphql',
],
documents,
preset: 'near-operation-file',
presetConfig: {
baseTypesPath: '~#shared/graphql/types.ts',

View File

@@ -55,6 +55,7 @@
"ignorePatterns": [
"app/frontend/**/graphql/**/*.ts",
"!app/frontend/tests/graphql/**/*.ts",
"app/frontend/shared/graphql/schema-types.ts",
"app/frontend/shared/graphql/types.ts",
"app/frontend/shared/types/config.ts"
]

View File

@@ -4,13 +4,13 @@
import { computed } from 'vue'
import { useTicketNumberAndTitle } from '#shared/entities/ticket/composables/useTicketNumberAndTitle.ts'
import type { TicketById } from '#shared/entities/ticket/types.ts'
import type { TicketLabel } from '#shared/entities/ticket/types.ts'
import { EnumTicketStateColorCode } from '#shared/graphql/types.ts'
import CommonTicketStateIndicatorIcon from '#desktop/components/CommonTicketStateIndicator/CommonTicketStateIndicatorIcon.vue'
interface Props {
ticket?: Partial<TicketById> | null
ticket?: TicketLabel | null
unauthorized?: boolean
noLink?: boolean
noWrap?: boolean

View File

@@ -2,9 +2,15 @@
import { nullableMock } from '#tests/support/utils.ts'
import type { AutocompleteSearchGenericEntry } from '#shared/graphql/types.ts'
import type { AutocompleteSearchGenericQuery } from '#shared/graphql/types.ts'
import { convertToGraphQLId } from '#shared/graphql/utils.ts'
type AutocompleteSearchGenericEntry =
AutocompleteSearchGenericQuery['autocompleteSearchGeneric'][number]
type GenericEntryObject = NonNullable<AutocompleteSearchGenericEntry['object']>
type GenericUser = Extract<GenericEntryObject, { __typename: 'User' }>
type GenericOrganization = Extract<GenericEntryObject, { __typename: 'Organization' }>
export const testOptions: AutocompleteSearchGenericEntry[] = [
{
__typename: 'AutocompleteSearchGenericEntry',
@@ -14,8 +20,7 @@ export const testOptions: AutocompleteSearchGenericEntry[] = [
heading: 'Zammad Foundation',
headingPlaceholder: [],
disabled: false,
icon: null,
object: nullableMock({
object: nullableMock<GenericUser>({
__typename: 'User',
id: convertToGraphQLId('User', 2),
internalId: 2,
@@ -31,12 +36,6 @@ export const testOptions: AutocompleteSearchGenericEntry[] = [
outOfOfficeEndAt: null,
active: true,
vip: false,
createdAt: '2022-11-30T12:40:15Z',
updatedAt: '2022-11-30T12:40:15Z',
policy: {
update: true,
destroy: false,
},
organization: null,
hasSecondaryOrganizations: false,
}),
@@ -49,8 +48,7 @@ export const testOptions: AutocompleteSearchGenericEntry[] = [
heading: '%s people',
headingPlaceholder: ['1'],
disabled: true,
icon: null,
object: nullableMock({
object: nullableMock<GenericOrganization>({
__typename: 'Organization',
id: convertToGraphQLId('Organization', 1),
internalId: 1,
@@ -61,7 +59,7 @@ export const testOptions: AutocompleteSearchGenericEntry[] = [
edges: [
{
__typename: 'UserEdge',
node: nullableMock({
node: nullableMock<GenericUser>({
__typename: 'User',
id: convertToGraphQLId('User', 1),
internalId: 1,
@@ -77,28 +75,9 @@ export const testOptions: AutocompleteSearchGenericEntry[] = [
outOfOfficeEndAt: null,
active: true,
vip: false,
createdAt: '2022-11-30T12:40:15Z',
updatedAt: '2022-11-30T12:40:15Z',
policy: {
update: true,
destroy: false,
},
}),
cursor: 'MH',
},
],
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: 'MH',
},
totalCount: 1,
},
createdAt: '2022-11-30T12:40:15Z',
updatedAt: '2022-11-30T12:40:15Z',
policy: {
update: true,
destroy: false,
},
}),
},

View File

@@ -2,9 +2,21 @@
import type { AutoCompleteCustomerGenericOption } from '#shared/components/Form/fields/FieldCustomer/types.ts'
import type { User } from '#shared/graphql/types.ts'
type AutocompleteCustomerUser = Extract<
NonNullable<AutoCompleteCustomerGenericOption['object']>,
{ __typename: 'User' }
>
export const useFieldCustomerOption = (object: User): AutoCompleteCustomerGenericOption => ({
__typename: 'AutocompleteSearchGenericEntry',
value: object.internalId,
label: (object.fullname || object.phone || object.login) as string,
labelPlaceholder: null,
heading: object.organization?.name,
object,
headingPlaceholder: null,
disabled: false,
object: {
...object,
__typename: 'User',
} as AutocompleteCustomerUser,
})

View File

@@ -4,6 +4,7 @@ import { flushPromises } from '@vue/test-utils'
import { ref } from 'vue'
import { renderComponent } from '#tests/support/components/index.ts'
import { nullableMock } from '#tests/support/utils.ts'
import type {
MentionKnowledgeBaseItem,
@@ -22,7 +23,7 @@ const baseProps = {
describe('component for rendering suggestions', () => {
it('renders knowledge base article', () => {
const items: MentionKnowledgeBaseItem[] = [
const items = nullableMock<MentionKnowledgeBaseItem[]>([
{
__typename: 'KnowledgeBaseAnswerTranslation',
id: convertToGraphQLId('KnowledgeBaseAnswerTranslation', 1),
@@ -74,7 +75,7 @@ describe('component for rendering suggestions', () => {
},
],
},
]
])
const view = renderComponent(FieldEditorSuggestionList, {
props: {
@@ -98,14 +99,14 @@ describe('component for rendering suggestions', () => {
})
it('renders text item', () => {
const items: MentionTextItem[] = [
const items = nullableMock<MentionTextItem[]>([
{
name: 'Text Item',
keywords: 'key',
renderedContent: 'content',
id: convertToGraphQLId('TextModule', 1),
},
]
])
const view = renderComponent(FieldEditorSuggestionList, {
props: {
@@ -121,14 +122,14 @@ describe('component for rendering suggestions', () => {
})
it('renders text item with spaces in search query', () => {
const items: MentionTextItem[] = [
const items = nullableMock<MentionTextItem[]>([
{
name: 'the best',
keywords: 'best',
renderedContent: 'wishing you all the best',
id: convertToGraphQLId('TextModule', 1),
},
]
])
const view = renderComponent(FieldEditorSuggestionList, {
props: {
@@ -144,7 +145,7 @@ describe('component for rendering suggestions', () => {
})
it('renders user mention', () => {
const items: MentionUserItem[] = [
const items = nullableMock<MentionUserItem[]>([
{
id: convertToGraphQLId('User', 1),
fullname: 'John Doe',
@@ -156,7 +157,7 @@ describe('component for rendering suggestions', () => {
fullname: 'Nicole Braun',
internalId: 2,
},
]
])
const view = renderComponent(FieldEditorSuggestionList, {
props: {
@@ -178,7 +179,7 @@ describe('component for rendering suggestions', () => {
})
it('renders user mention with spaces in search query', () => {
const items: MentionUserItem[] = [
const items = nullableMock<MentionUserItem[]>([
{
id: convertToGraphQLId('User', 1),
fullname: 'John Doe',
@@ -190,7 +191,7 @@ describe('component for rendering suggestions', () => {
fullname: 'Nicole Braun',
internalId: 2,
},
]
])
const view = renderComponent(FieldEditorSuggestionList, {
props: {
@@ -209,7 +210,7 @@ describe('component for rendering suggestions', () => {
})
describe('actions in list', () => {
const items: MentionUserItem[] = [
const items = nullableMock<MentionUserItem[]>([
{
id: convertToGraphQLId('User', 1),
fullname: 'John Doe',
@@ -225,7 +226,7 @@ describe('actions in list', () => {
fullname: 'Erik Wise',
internalId: 3,
},
]
])
const renderList = () => {
const listExposed = ref<{
@@ -312,7 +313,7 @@ describe('actions in list', () => {
})
describe('accessibility (combobox + listbox wiring)', () => {
const items: MentionTextItem[] = [
const items = nullableMock<MentionTextItem[]>([
{
name: 'Greeting',
keywords: 'hi',
@@ -325,7 +326,7 @@ describe('accessibility (combobox + listbox wiring)', () => {
renderedContent: 'Goodbye',
id: convertToGraphQLId('TextModule', 2),
},
]
])
it('uses the label prop as the listbox aria-label', () => {
const view = renderComponent(FieldEditorSuggestionList, {

View File

@@ -5,7 +5,36 @@ import type { Organization } from '#shared/graphql/types.ts'
export const useFieldOrganizationOption = (
organization: Organization,
): AutoCompleteOrganizationOption => ({
__typename: 'AutocompleteSearchOrganizationEntry',
value: organization.internalId,
label: organization.name as string,
organization,
labelPlaceholder: null,
heading: null,
headingPlaceholder: null,
disabled: false,
icon: null,
organization: {
__typename: 'Organization',
id: organization.id,
internalId: organization.internalId,
name: organization.name,
shared: organization.shared,
domain: organization.domain,
domainAssignment: organization.domainAssignment,
active: organization.active,
note: organization.note,
vip: organization.vip,
objectAttributeValues: organization.objectAttributeValues?.map(
({ attribute, value, renderedLink }) => ({
__typename: 'ObjectAttributeValue',
value,
renderedLink,
attribute: {
__typename: 'ObjectManagerFrontendAttribute',
name: attribute.name,
display: attribute.display,
},
}),
),
},
})

View File

@@ -29,10 +29,12 @@ const testOptions: AutocompleteSearchTicketQuery['autocompleteSearchTicket'][0][
disabled: false,
icon: null,
ticket: nullableMock({
__typename: 'Ticket',
id: convertToGraphQLId('Ticket', 1),
internalId: 1,
number: '123456',
state: nullableMock({
__typename: 'TicketState' as const,
id: convertToGraphQLId('TicketState', 1),
name: 'open',
}),
@@ -49,10 +51,12 @@ const testOptions: AutocompleteSearchTicketQuery['autocompleteSearchTicket'][0][
disabled: false,
icon: null,
ticket: nullableMock({
__typename: 'Ticket',
id: convertToGraphQLId('Ticket', 2),
internalId: 1,
number: '55555',
state: nullableMock({
__typename: 'TicketState' as const,
id: convertToGraphQLId('TicketState', 1),
name: 'open',
}),
@@ -69,10 +73,12 @@ const testOptions: AutocompleteSearchTicketQuery['autocompleteSearchTicket'][0][
disabled: false,
icon: null,
ticket: nullableMock({
__typename: 'Ticket',
id: convertToGraphQLId('Ticket', 3),
internalId: 1,
number: '99999',
state: nullableMock({
__typename: 'TicketState' as const,
id: convertToGraphQLId('TicketState', 1),
name: 'open',
}),

View File

@@ -18,6 +18,7 @@ export const DetailSearchDocument = gql`
) {
totalCount
items {
__typename
... on Ticket {
id
internalId

View File

@@ -18,6 +18,7 @@ query detailSearch(
) {
totalCount
items {
__typename
... on Ticket {
id
internalId

View File

@@ -14,6 +14,7 @@ export const QuickSearchDocument = gql`
) {
totalCount
items {
__typename
... on Organization {
id
internalId
@@ -25,6 +26,7 @@ export const QuickSearchDocument = gql`
quickSearchTickets: search(search: $search, onlyIn: Ticket, limit: $limit) {
totalCount
items {
__typename
... on Ticket {
id
internalId
@@ -41,6 +43,7 @@ export const QuickSearchDocument = gql`
quickSearchUsers: search(search: $search, onlyIn: User, limit: $limit) {
totalCount
items {
__typename
... on User {
id
internalId

View File

@@ -2,6 +2,7 @@ query quickSearch($search: String!, $limit: Int = 10) {
quickSearchOrganizations: search(search: $search, onlyIn: Organization, limit: $limit) {
totalCount
items {
__typename
... on Organization {
id
internalId
@@ -13,6 +14,7 @@ query quickSearch($search: String!, $limit: Int = 10) {
quickSearchTickets: search(search: $search, onlyIn: Ticket, limit: $limit) {
totalCount
items {
__typename
... on Ticket {
id
internalId
@@ -29,6 +31,7 @@ query quickSearch($search: String!, $limit: Int = 10) {
quickSearchUsers: search(search: $search, onlyIn: User, limit: $limit) {
totalCount
items {
__typename
... on User {
id
internalId

View File

@@ -10,8 +10,10 @@ export const SearchTaskbarItemStateUpdatesDocument = gql`
userCurrentTaskbarItemStateUpdates(taskbarItemId: $taskbarItemId) {
stateUpdateType
taskbarItem {
__typename
id
entity {
__typename
... on UserTaskbarItemEntitySearch {
query
model

View File

@@ -2,8 +2,10 @@ subscription searchTaskbarItemStateUpdates($taskbarItemId: ID!) {
userCurrentTaskbarItemStateUpdates(taskbarItemId: $taskbarItemId) {
stateUpdateType
taskbarItem {
__typename
id
entity {
__typename
... on UserTaskbarItemEntitySearch {
query
model

View File

@@ -40,7 +40,7 @@ const router = useRouter()
const goToUserProfile = () => {
if (!props.filters.customerId) return
router.push(`/users/${getIdFromGraphQLId(props.filters.customerId)}`)
router.push(`/users/${getIdFromGraphQLId(String(props.filters.customerId))}`)
}
</script>

View File

@@ -3,8 +3,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import type { TicketById } from '#shared/entities/ticket/types.ts'
import type { Ticket } from '#shared/graphql/types.ts'
import type { TicketLabel } from '#shared/entities/ticket/types.ts'
import { useSessionStore } from '#shared/stores/session.ts'
import { type Props as CommonPopoverProps } from '#desktop//components/CommonPopover/CommonPopover.vue'
@@ -14,7 +13,7 @@ import CommonTicketLabel from '#desktop/components/CommonTicketLabel/CommonTicke
import TicketPopover from './TicketPopoverWithTrigger/TicketPopover.vue'
export interface Props {
ticket: Partial<Ticket | TicketById>
ticket: TicketLabel
popoverConfig?: Omit<CommonPopoverProps, 'owner'>
triggerClass?: string
noTriggerLink?: boolean

View File

@@ -5,13 +5,8 @@ import { computed } from 'vue'
import ObjectAttributes from '#shared/components/ObjectAttributes/ObjectAttributes.vue'
import { useObjectAttributes } from '#shared/entities/object-attributes/composables/useObjectAttributes.ts'
import type { TicketById } from '#shared/entities/ticket/types.ts'
import {
EnumObjectManagerObjects,
type Organization,
type Ticket,
type User,
} from '#shared/graphql/types.ts'
import type { TicketById, TicketLabel } from '#shared/entities/ticket/types.ts'
import { EnumObjectManagerObjects, type Organization, type User } from '#shared/graphql/types.ts'
import QueryHandler from '#shared/server/apollo/handler/QueryHandler.ts'
import { SYSTEM_USER_ID } from '#shared/utils/constants.ts'
@@ -25,7 +20,7 @@ import { useTicketInfoForPopoverQuery } from './graphql/queries/ticketInfoForPop
import TicketPopoverSkeleton from './skeleton/TicketPopoverSkeleton.vue'
interface Props {
ticket: Partial<Ticket | TicketById>
ticket: TicketLabel
}
const props = defineProps<Props>()

View File

@@ -3,7 +3,7 @@
import '#tests/graphql/builders/mocks.ts'
import { renderComponent, type ExtendedMountingOptions } from '#tests/support/components/index.ts'
import { mockRouterHooks } from '#tests/support/mock-vue-router.ts'
import { waitForNextTick } from '#tests/support/utils.ts'
import { nullableMock, waitForNextTick } from '#tests/support/utils.ts'
import type { TicketByList } from '#shared/entities/ticket/types.ts'
import { EnumTicketStateColorCode } from '#shared/graphql/types.ts'
@@ -37,7 +37,8 @@ const renderListTable = async (
const tableHeaders = ['title', 'owner', 'state', 'stateIcon', 'priority']
const tableItems: TicketByList[] = [
{
nullableMock<TicketByList>({
__typename: 'Ticket',
id: convertToGraphQLId('Ticket', 1),
title: 'Dummy ticket',
owner: {
@@ -59,6 +60,7 @@ const tableItems: TicketByList[] = [
__typename: 'TicketPriority',
id: convertToGraphQLId('TicketPriority', 3),
name: '3 high',
uiColor: null,
},
createdAt: '2021-01-01T12:00:00Z',
internalId: 0,
@@ -66,20 +68,20 @@ const tableItems: TicketByList[] = [
updatedAt: '',
stateColorCode: EnumTicketStateColorCode.Closed,
customer: {
__typename: undefined,
__typename: 'User',
id: '',
fullname: undefined,
},
group: {
__typename: undefined,
__typename: 'Group',
id: '',
name: undefined,
},
policy: {
__typename: undefined,
__typename: 'PolicyTicket',
update: false,
},
},
}),
]
describe('TicketListTable', () => {

View File

@@ -3,10 +3,9 @@
import type { FormSchemaField } from '#shared/components/Form/types.ts'
import { useOrganizationFormSchema } from '#shared/entities/organization/composables/useOrganizationFormSchema.ts'
import { useOrganizationUpdateMutation } from '#shared/entities/organization/graphql/mutations/update.api.ts'
import type { EditableOrganization } from '#shared/entities/organization/types.ts'
import { defineFormSchema } from '#shared/form/defineFormSchema.ts'
import type { OrganizationQuery } from '#shared/graphql/types.ts'
import { EnumFormUpdaterId, EnumObjectManagerObjects } from '#shared/graphql/types.ts'
import type { ConfidentTake } from '#shared/types/utils.ts'
import { openFlyout } from '#desktop/components/CommonFlyout/useFlyout.ts'
import { useFlyoutObjectForm } from '#desktop/components/CommonFlyoutObjectForm/useFlyoutObjectForm.ts'
@@ -46,7 +45,7 @@ const buildOrganizationEditFormChangeFields = (): Record<string, Partial<FormSch
}
export const openOrganizationEditFlyout = async (
organization: ConfidentTake<OrganizationQuery, 'organization'>,
organization: EditableOrganization,
options?: { title: string },
) => {
const formChangeFields = buildOrganizationEditFormChangeFields()

View File

@@ -3,11 +3,10 @@
import type { FormSchemaField } from '#shared/components/Form/types.ts'
import { useUserFormSchema } from '#shared/entities/user/composables/useUserFormSchema.ts'
import { useUserUpdateMutation } from '#shared/entities/user/graphql/mutations/update.api.ts'
import type { EditableUser } from '#shared/entities/user/types.ts'
import { defineFormSchema } from '#shared/form/defineFormSchema.ts'
import type { UserQuery } from '#shared/graphql/types.ts'
import { EnumFormUpdaterId, EnumObjectManagerObjects } from '#shared/graphql/types.ts'
import { useApplicationStore } from '#shared/stores/application.ts'
import type { ConfidentTake } from '#shared/types/utils.ts'
import { openFlyout } from '#desktop/components/CommonFlyout/useFlyout.ts'
import { useFlyoutObjectForm } from '#desktop/components/CommonFlyoutObjectForm/useFlyoutObjectForm.ts'
@@ -49,10 +48,7 @@ const buildUserEditFormChangeFields = (): Record<string, Partial<FormSchemaField
}
}
export const openUserEditFlyout = async (
user: ConfidentTake<UserQuery, 'user'>,
options?: { title: string },
) => {
export const openUserEditFlyout = async (user: EditableUser, options?: { title: string }) => {
const application = useApplicationStore()
const formChangeFields = buildUserEditFormChangeFields()

View File

@@ -10,6 +10,7 @@ export const UserCurrentTaskbarItemAttributesFragmentDoc = gql`
formId
formNewArticlePresent
entity {
__typename
... on Ticket {
...ticketTaskbarTabAttributes
}

View File

@@ -5,6 +5,7 @@ fragment userCurrentTaskbarItemAttributes on UserTaskbarItem {
formId
formNewArticlePresent
entity {
__typename
... on Ticket {
...ticketTaskbarTabAttributes
}

View File

@@ -8,6 +8,7 @@ export type ReactiveFunction<TParam> = () => TParam;
export const UserCurrentRecentCloseListDocument = gql`
query userCurrentRecentCloseList($limit: Int) {
userCurrentRecentCloseList(limit: $limit) {
__typename
... on Ticket {
id
internalId

View File

@@ -1,5 +1,6 @@
query userCurrentRecentCloseList($limit: Int) {
userCurrentRecentCloseList(limit: $limit) {
__typename
... on Ticket {
id
internalId

View File

@@ -13,6 +13,7 @@ import { EnumTwoFactorAuthenticationMethod, type LoginMutation } from '#shared/g
import { GraphQLErrorTypes } from '#shared/types/error.ts'
const twoFactorAuthentication = () => ({
__typename: 'UserLoginTwoFactorMethods' as const,
availableTwoFactorAuthenticationMethods: [EnumTwoFactorAuthenticationMethod.AuthenticatorApp],
defaultTwoFactorAuthenticationMethod: EnumTwoFactorAuthenticationMethod.AuthenticatorApp,
recoveryCodesAvailable: false,
@@ -148,6 +149,7 @@ describe('two-factor login flow', () => {
describe('alternative two-factor methods', () => {
it.each([
{
__typename: 'UserLoginTwoFactorMethods' as const,
availableTwoFactorAuthenticationMethods: [
EnumTwoFactorAuthenticationMethod.AuthenticatorApp,
],
@@ -157,6 +159,7 @@ describe('two-factor login flow', () => {
available: false,
},
{
__typename: 'UserLoginTwoFactorMethods' as const,
availableTwoFactorAuthenticationMethods: [
EnumTwoFactorAuthenticationMethod.AuthenticatorApp,
],
@@ -166,6 +169,7 @@ describe('two-factor login flow', () => {
available: true,
},
{
__typename: 'UserLoginTwoFactorMethods' as const,
availableTwoFactorAuthenticationMethods: [
EnumTwoFactorAuthenticationMethod.AuthenticatorApp,
EnumTwoFactorAuthenticationMethod.SecurityKeys,

View File

@@ -10,12 +10,15 @@ import securityKeys from '#shared/entities/two-factor/plugins/security-keys.ts'
import type { TwoFactorPlugin, TwoFactorSetupResult } from '#shared/entities/two-factor/types.ts'
import { TwoFactorMethodInitiateAuthenticationDocument } from '#shared/graphql/mutations/twoFactorMethodInitiateAuthentication.api.ts'
import type { TwoFactorMethodInitiateAuthenticationMutation } from '#shared/graphql/types.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
import { createDeferred } from '#shared/utils/helpers.ts'
import LoginTwoFactor from '../LoginTwoFactor.vue'
const prepareInitialData = (
data: TwoFactorMethodInitiateAuthenticationMutation['twoFactorMethodInitiateAuthentication'],
data: DeepPartial<
TwoFactorMethodInitiateAuthenticationMutation['twoFactorMethodInitiateAuthentication']
>,
) => {
mockGraphQLResult<TwoFactorMethodInitiateAuthenticationMutation>(
TwoFactorMethodInitiateAuthenticationDocument,

View File

@@ -86,7 +86,8 @@ const schema = defineFormSchema([
])
const initialFormValues = computed<NotificationFormData>((oldValues) => {
const { notificationConfig = {}, notificationSound = {} } = user.value?.personalSettings || {}
const notificationConfig = user.value?.personalSettings?.notificationConfig
const notificationSound = user.value?.personalSettings?.notificationSound
const values: NotificationFormData = {
group_ids: notificationConfig?.groupIds ?? [],

View File

@@ -12,8 +12,8 @@ import { useTicketSidebar } from '#desktop/pages/ticket/composables/useTicketSid
const { ticket } = useTicketInformation()
const ticketSidebar = useTicketSidebar()
const referencingTickets = computed(
() => ticket?.value?.referencingChecklistTickets as ReferencingTicket[],
const referencingTickets = computed<ReferencingTicket[]>(
() => ticket?.value?.referencingChecklistTickets ?? [],
)
const checklist = computed(() => ticket?.value?.checklist)

View File

@@ -1,13 +1,10 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
import type { Ticket } from '#shared/graphql/types.ts'
import type { ReferencingTicket } from '#shared/entities/ticket/types.ts'
import type { MenuItem } from '#desktop/components/CommonPopoverMenu/types.ts'
export type ReferencingTicket = Pick<
Ticket,
'id' | 'internalId' | 'number' | 'title' | 'state' | 'stateColorCode'
>
export type { ReferencingTicket } from '#shared/entities/ticket/types.ts'
export interface TicketReferenceMenuItem extends MenuItem {
ticket: ReferencingTicket

View File

@@ -6,7 +6,7 @@ import type { TicketById } from '#shared/entities/ticket/types.ts'
import { EnumTicketStateColorCode } from '#shared/graphql/types.ts'
import { convertToGraphQLId } from '#shared/graphql/utils.ts'
export const testOptionsTopBar: TicketById = nullableMock({
export const testOptionsTopBar: TicketById = nullableMock<TicketById>({
__typename: 'Ticket',
createArticleType: {
__typename: 'TicketArticleType',

View File

@@ -2,7 +2,6 @@
import { computed, toRef, type Ref } from 'vue'
import type { TicketById } from '#shared/entities/ticket/types.ts'
import { EnumTicketScreenBehavior } from '#shared/graphql/types.ts'
import { useWalker } from '#shared/router/walker.ts'
import { useSessionStore } from '#shared/stores/session.ts'
@@ -34,7 +33,13 @@ export const useTicketScreenBehavior = (currentTaskbarTabId: Ref<string | undefi
ticket,
}: {
screenBehaviour?: EnumTicketScreenBehavior
ticket: TicketById
ticket: {
state: {
stateType: {
name: string
}
}
}
}) => {
const currentScreenBehaviour = screenBehaviour || secondaryAction.value

View File

@@ -16,6 +16,7 @@ import {
type TicketChecklistUpdatesSubscription,
} from '#shared/graphql/types.ts'
import { convertToGraphQLId } from '#shared/graphql/utils.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
import '#tests/graphql/builders/mocks.ts'
import { waitForTicketChecklistAddMutationCalls } from '#desktop/pages/ticket/graphql/mutations/ticketChecklistAdd.mocks.ts'
@@ -76,63 +77,78 @@ const templateMocks: Partial<ChecklistTemplate>[] = [
},
]
const checklistItemsMock: Partial<ChecklistItem>[] = [
{
const checklistItemsMock: ChecklistItem[] = [
nullableMock<ChecklistItem>({
__typename: 'ChecklistItem',
id: convertToGraphQLId('Checklist::Item', 1),
text: 'Checklist item A',
checked: false,
ticketReference: null,
},
{
}),
nullableMock<ChecklistItem>({
__typename: 'ChecklistItem',
id: convertToGraphQLId('Checklist::Item', 2),
text: 'Checklist item B',
checked: false,
ticketReference: null,
},
}),
]
type TicketChecklist = NonNullable<
TicketChecklistUpdatesSubscription['ticketChecklistUpdates']['ticketChecklist']
>
// Overrides the default checklist values if set to null, checklist will be empty
const mockChecklistUpdateSubscription = async (
partialTicketChecklist: Partial<
TicketChecklistUpdatesSubscription['ticketChecklistUpdates']['ticketChecklist']
> | null,
partialTicketChecklist: DeepPartial<TicketChecklist> | null,
) => {
if (partialTicketChecklist === null) {
await getTicketChecklistUpdatesSubscriptionHandler().trigger({
ticketChecklistUpdates: {
ticketChecklist: null,
},
})
await getTicketChecklistUpdatesSubscriptionHandler().trigger(
nullableMock<TicketChecklistUpdatesSubscription>({
ticketChecklistUpdates: {
__typename: 'TicketChecklistUpdatesPayload',
removedTicketChecklist: null,
ticketChecklist: null,
},
}),
)
return
}
const ticketChecklist = {
const ticketChecklist = nullableMock<TicketChecklist>({
__typename: 'Checklist',
completed: false,
incomplete: 2,
name: 'Checklist title',
id: convertToGraphQLId('Checklist', 1),
items: [
{
__typename: 'ChecklistItem',
checked: false,
id: convertToGraphQLId('Checklist::Item', 1),
text: 'Checklist item A',
ticketReference: null,
},
{
__typename: 'ChecklistItem',
checked: false,
id: convertToGraphQLId('Checklist::Item', 2),
text: 'Checklist item B',
ticketReference: null,
},
],
...partialTicketChecklist,
}
await getTicketChecklistUpdatesSubscriptionHandler().trigger({
ticketChecklistUpdates: {
removedTicketChecklist: null,
ticketChecklist,
},
})
await getTicketChecklistUpdatesSubscriptionHandler().trigger(
nullableMock<TicketChecklistUpdatesSubscription>({
ticketChecklistUpdates: {
__typename: 'TicketChecklistUpdatesPayload',
removedTicketChecklist: null,
ticketChecklist,
},
}),
)
}
const openMenuAndClickAction = async (

View File

@@ -203,6 +203,7 @@ describe('TicketSidebarCustomerContent.vue', () => {
{
...defaultTicket,
policy: {
__typename: 'PolicyTicket',
update: false,
agentReadAccess: true,
},

View File

@@ -14,31 +14,37 @@ const createTimeAccountingTicket = (
const timeUnitsPerType = [
{
__typename: 'TicketTimeAccountingTypeSum' as const,
name: 'None',
timeUnit: 6,
timeUnitDisplay: '6.00',
},
{
__typename: 'TicketTimeAccountingTypeSum' as const,
name: 'Finance',
timeUnit: 5,
timeUnitDisplay: '5.00',
},
{
__typename: 'TicketTimeAccountingTypeSum' as const,
name: 'Business',
timeUnit: 4,
timeUnitDisplay: '4.00',
},
{
__typename: 'TicketTimeAccountingTypeSum' as const,
name: 'Development',
timeUnit: 3,
timeUnitDisplay: '3.00',
},
{
__typename: 'TicketTimeAccountingTypeSum' as const,
name: 'Testing',
timeUnit: 2,
timeUnitDisplay: '2.00',
},
{
__typename: 'TicketTimeAccountingTypeSum' as const,
name: 'Foo',
timeUnit: 1,
timeUnitDisplay: '1.00',

View File

@@ -118,6 +118,7 @@ describe('TicketSidebarInformationContent', () => {
const wrapper = renderInformationSidebar({
...defaultTicket,
policy: {
__typename: 'PolicyTicket',
update: false,
agentReadAccess: true,
},
@@ -138,6 +139,7 @@ describe('TicketSidebarInformationContent', () => {
const wrapper = renderInformationSidebar({
...defaultTicket,
policy: {
__typename: 'PolicyTicket',
update: true,
agentReadAccess: false,
},

View File

@@ -4,7 +4,7 @@ import { getNode } from '@formkit/core'
import { renderComponent } from '#tests/support/components/index.ts'
import { mockRouterHooks } from '#tests/support/mock-vue-router.ts'
import { waitForNextTick } from '#tests/support/utils.ts'
import { nullableMock, waitForNextTick } from '#tests/support/utils.ts'
import { waitForTicketSharedDraftStartCreateMutationCalls } from '#shared/entities/ticket-shared-draft-start/graphql/mutations/ticketSharedDraftStartCreate.mocks.ts'
import { useTicketSharedDraftStartDeleteMutation } from '#shared/entities/ticket-shared-draft-start/graphql/mutations/ticketSharedDraftStartDelete.api.ts'
@@ -35,6 +35,36 @@ vi.mock('#desktop/components/CommonFlyout/useFlyout.ts', async (importOriginal)
mockRouterHooks()
type SharedDraftStartItem = TicketSharedDraftStartListQuery['ticketSharedDraftStartList'][number]
const sharedDraftStart = (
id: number,
name: string,
updatedAt: string,
updatedBy?: {
id: number
firstname: string
lastname: string
fullname: string
},
): SharedDraftStartItem =>
nullableMock<SharedDraftStartItem>({
__typename: 'TicketSharedDraftStart',
id: convertToGraphQLId('Ticket::SharedDraftStart', id),
name,
updatedAt,
updatedBy: updatedBy
? {
__typename: 'User',
id: convertToGraphQLId('User', updatedBy.id),
internalId: updatedBy.id,
firstname: updatedBy.firstname,
lastname: updatedBy.lastname,
fullname: updatedBy.fullname,
}
: null,
})
const renderTicketSidebarSharedDraftStartContent = async (
sharedDraftStartList: TicketSharedDraftStartListQuery['ticketSharedDraftStartList'],
context: {
@@ -83,37 +113,19 @@ describe('TicketSidebarSharedDraftStartContent.vue', () => {
it('renders non-empty shared draft list', async () => {
const wrapper = await renderTicketSidebarSharedDraftStartContent(
[
{
id: convertToGraphQLId('Ticket::SharedDraftStart', 1),
name: 'Test shared draft 1',
updatedAt: '2024-07-03T13:48:09Z',
updatedBy: {
__typename: 'User',
id: convertToGraphQLId('User', 2),
internalId: 2,
firstname: 'Erika',
lastname: 'Mustermann',
fullname: 'Erika Mustermann',
},
},
{
id: convertToGraphQLId('Ticket::SharedDraftStart', 2),
name: 'Test shared draft 2',
updatedAt: '2024-07-03T13:30:00Z',
updatedBy: {
__typename: 'User',
id: convertToGraphQLId('User', 3),
internalId: 3,
firstname: 'Max',
lastname: 'Mustermann',
fullname: 'Max Mustermann',
},
},
{
id: convertToGraphQLId('Ticket::SharedDraftStart', 3),
name: 'Test shared draft 3',
updatedAt: '2024-07-02T12:00:00Z',
},
sharedDraftStart(1, 'Test shared draft 1', '2024-07-03T13:48:09Z', {
id: 2,
firstname: 'Erika',
lastname: 'Mustermann',
fullname: 'Erika Mustermann',
}),
sharedDraftStart(2, 'Test shared draft 2', '2024-07-03T13:30:00Z', {
id: 3,
firstname: 'Max',
lastname: 'Mustermann',
fullname: 'Max Mustermann',
}),
sharedDraftStart(3, 'Test shared draft 3', '2024-07-02T12:00:00Z'),
],
{
formValues: {
@@ -140,19 +152,12 @@ describe('TicketSidebarSharedDraftStartContent.vue', () => {
it('supports previewing shared draft', async () => {
const wrapper = await renderTicketSidebarSharedDraftStartContent(
[
{
id: convertToGraphQLId('Ticket::SharedDraftStart', 1),
name: 'Test shared draft 1',
updatedAt: '2024-07-03T13:48:09Z',
updatedBy: {
__typename: 'User',
id: convertToGraphQLId('User', 2),
internalId: 2,
firstname: 'Erika',
lastname: 'Mustermann',
fullname: 'Erika Mustermann',
},
},
sharedDraftStart(1, 'Test shared draft 1', '2024-07-03T13:48:09Z', {
id: 2,
firstname: 'Erika',
lastname: 'Mustermann',
fullname: 'Erika Mustermann',
}),
],
{
formValues: {
@@ -230,19 +235,12 @@ describe('TicketSidebarSharedDraftStartContent.vue', () => {
it('supports updating existing shared draft', async () => {
const wrapper = await renderTicketSidebarSharedDraftStartContent(
[
{
id: convertToGraphQLId('Ticket::SharedDraftStart', 1),
name: 'Test shared draft 1',
updatedAt: '2024-07-03T13:48:09Z',
updatedBy: {
__typename: 'User',
id: convertToGraphQLId('User', 2),
internalId: 2,
firstname: 'Erika',
lastname: 'Mustermann',
fullname: 'Erika Mustermann',
},
},
sharedDraftStart(1, 'Test shared draft 1', '2024-07-03T13:48:09Z', {
id: 2,
firstname: 'Erika',
lastname: 'Mustermann',
fullname: 'Erika Mustermann',
}),
],
{
formValues: {

View File

@@ -233,9 +233,11 @@ describe('TicketSidebarSummary', () => {
it('hides sidebar when ticket got merged', async () => {
const wrapper = renderRenderTicketSidebarSummary({
state: {
__typename: 'TicketState',
name: 'merged',
id: convertToGraphQLId('State', 5),
stateType: {
__typename: 'TicketStateType',
id: convertToGraphQLId('StateType', 6),
name: 'merged',
},

View File

@@ -2,10 +2,10 @@
import { computed, toRef, type Ref } from 'vue'
import type { TicketById } from '#shared/entities/ticket/types.ts'
import type { TicketLabel } from '#shared/entities/ticket/types.ts'
import { useApplicationStore } from '#shared/stores/application.ts'
export const useTicketNumber = (ticket: Ref<TicketById | undefined>) => {
export const useTicketNumber = (ticket: Ref<TicketLabel | undefined>) => {
const ticketNumber = computed(() => ticket?.value?.number?.toString())
const config = toRef(useApplicationStore(), 'config')

View File

@@ -11,6 +11,7 @@ export const LinkAddDocument = gql`
link {
type
item {
__typename
... on Ticket {
id
internalId

View File

@@ -3,6 +3,7 @@ mutation linkAdd($input: LinkInput!) {
link {
type
item {
__typename
... on Ticket {
id
internalId

View File

@@ -10,6 +10,7 @@ export const LinkListDocument = gql`
linkList(objectId: $objectId, targetType: $targetType) {
type
item {
__typename
... on Ticket {
id
internalId

View File

@@ -2,6 +2,7 @@ query linkList($objectId: ID!, $targetType: String!) {
linkList(objectId: $objectId, targetType: $targetType) {
type
item {
__typename
... on Ticket {
id
internalId

View File

@@ -10,6 +10,7 @@ export const LinkUpdatesDocument = gql`
linkUpdates(objectId: $objectId, targetType: $targetType) {
links {
item {
__typename
... on Ticket {
id
internalId

View File

@@ -2,6 +2,7 @@ subscription linkUpdates($objectId: ID!, $targetType: String!) {
linkUpdates(objectId: $objectId, targetType: $targetType) {
links {
item {
__typename
... on Ticket {
id
internalId

View File

@@ -20,8 +20,20 @@ import type { FormKitNode } from '@formkit/core'
const mockQueryResult = (input: { query: string; limit: number }): AutocompleteSearchUserQuery => {
const options = testOptions.map((option) =>
nullableMock({
nullableMock<AutocompleteSearchUserEntry>({
...option,
user: {
...option.user,
__typename: 'User',
id: String(option.user.id),
organization: option.user.organization
? {
...option.user.organization,
__typename: 'Organization',
id: String(option.user.organization.id),
}
: null,
},
labelPlaceholder: null,
headingPlaceholder: null,
disabled: null,
@@ -39,12 +51,12 @@ const mockQueryResult = (input: { query: string; limit: number }): AutocompleteS
// Search across options via their de-accented labels.
const filteredOptions = options.filter(
(option) =>
filterRegex.test(deaccent(option.label)) || filterRegex.test(deaccent(option.heading)),
filterRegex.test(deaccent(option.label)) || filterRegex.test(deaccent(option.heading || '')),
) as unknown as AutocompleteSearchUserEntry[]
return {
return nullableMock<AutocompleteSearchUserQuery>({
autocompleteSearchUser: filteredOptions.slice(0, input.limit ?? 25),
}
})
}
const wrapperParameters = {

View File

@@ -6,8 +6,10 @@ import { ref } from 'vue'
import { renderComponent } from '#tests/support/components/index.ts'
import { mockApplicationConfig } from '#tests/support/mock-applicationConfig.ts'
import { mockPermissions } from '#tests/support/mock-permissions.ts'
import { nullableMock } from '#tests/support/utils.ts'
import { mockAiAssistanceTextToolsRunMutation } from '#shared/graphql/mutations/aiAssistanceTextToolsRun.mocks.ts'
import type { AiAssistanceTextToolsListQuery } from '#shared/graphql/types.ts'
import { useAiAssistantTextToolsStore } from '#shared/stores/aiAssistantTextTools.ts'
import getUuid from '#shared/utils/getUuid.ts'
@@ -312,15 +314,18 @@ describe('basic toolbar testing', () => {
const store = useAiAssistantTextToolsStore()
vi.spyOn(store, 'lookupResult').mockReturnValue(
ref({
aiAssistanceTextToolsList: [
{
id: 'expand-tool-123',
name: 'Expand',
active: true,
},
],
}),
ref(
nullableMock<AiAssistanceTextToolsListQuery>({
aiAssistanceTextToolsList: [
{
__typename: 'AITextTool',
id: 'expand-tool-123',
name: 'Expand',
active: true,
},
],
}),
),
)
const wrapper = renderComponent(FieldEditorActionBar, {

View File

@@ -4,6 +4,7 @@ import { flushPromises } from '@vue/test-utils'
import { ref } from 'vue'
import { renderComponent } from '#tests/support/components/index.ts'
import { nullableMock } from '#tests/support/utils.ts'
import type {
MentionKnowledgeBaseItem,
@@ -22,7 +23,7 @@ const baseProps = {
describe('component for rendering suggestions', () => {
it('renders knowledge base article', () => {
const items: MentionKnowledgeBaseItem[] = [
const items = nullableMock<MentionKnowledgeBaseItem[]>([
{
__typename: 'KnowledgeBaseAnswerTranslation',
id: convertToGraphQLId('KnowledgeBaseAnswerTranslation', 1),
@@ -52,7 +53,7 @@ describe('component for rendering suggestions', () => {
},
],
},
]
])
const view = renderComponent(FieldEditorSuggestionList, {
props: {
@@ -71,14 +72,14 @@ describe('component for rendering suggestions', () => {
})
it('renders text item', () => {
const items: MentionTextItem[] = [
const items = nullableMock<MentionTextItem[]>([
{
name: 'Text Item',
keywords: 'key',
renderedContent: 'content',
id: convertToGraphQLId('TextModule', 1),
},
]
])
const view = renderComponent(FieldEditorSuggestionList, {
props: {
@@ -94,14 +95,14 @@ describe('component for rendering suggestions', () => {
})
it('renders text item with spaces in search query', () => {
const items: MentionTextItem[] = [
const items = nullableMock<MentionTextItem[]>([
{
name: 'the best',
keywords: 'best',
renderedContent: 'wishing you all the best',
id: convertToGraphQLId('TextModule', 1),
},
]
])
const view = renderComponent(FieldEditorSuggestionList, {
props: {
@@ -117,7 +118,7 @@ describe('component for rendering suggestions', () => {
})
it('renders user mention', () => {
const items: MentionUserItem[] = [
const items = nullableMock<MentionUserItem[]>([
{
id: convertToGraphQLId('User', 1),
fullname: 'John Doe',
@@ -129,7 +130,7 @@ describe('component for rendering suggestions', () => {
fullname: 'Nicole Braun',
internalId: 2,
},
]
])
const view = renderComponent(FieldEditorSuggestionList, {
props: {
@@ -146,7 +147,7 @@ describe('component for rendering suggestions', () => {
})
it('renders user mention with spaces in search query', () => {
const items: MentionUserItem[] = [
const items = nullableMock<MentionUserItem[]>([
{
id: convertToGraphQLId('User', 1),
fullname: 'John Doe',
@@ -158,7 +159,7 @@ describe('component for rendering suggestions', () => {
fullname: 'Nicole Braun',
internalId: 2,
},
]
])
const view = renderComponent(FieldEditorSuggestionList, {
props: {
@@ -175,7 +176,7 @@ describe('component for rendering suggestions', () => {
})
describe('actions in list', () => {
const items: MentionUserItem[] = [
const items = nullableMock<MentionUserItem[]>([
{
id: convertToGraphQLId('User', 1),
fullname: 'John Doe',
@@ -191,7 +192,7 @@ describe('actions in list', () => {
fullname: 'Erik Wise',
internalId: 3,
},
]
])
const renderList = () => {
const listExposed = ref<{
@@ -276,7 +277,7 @@ describe('actions in list', () => {
})
describe('accessibility (combobox + listbox wiring)', () => {
const items: MentionTextItem[] = [
const items = nullableMock<MentionTextItem[]>([
{
name: 'Greeting',
keywords: 'hi',
@@ -289,7 +290,7 @@ describe('accessibility (combobox + listbox wiring)', () => {
renderedContent: 'Goodbye',
id: convertToGraphQLId('TextModule', 2),
},
]
])
it('uses the label prop as the listbox aria-label', () => {
const view = renderComponent(FieldEditorSuggestionList, {

View File

@@ -25,8 +25,13 @@ const mockQueryResult = (input: {
limit: number
}): AutocompleteSearchOrganizationQuery => {
const options = testOptions.map((option) =>
nullableMock({
nullableMock<AutocompleteSearchOrganizationEntry>({
...option,
organization: {
...option.organization,
__typename: 'Organization',
id: String(option.organization.id),
},
labelPlaceholder: null,
headingPlaceholder: null,
disabled: null,
@@ -44,12 +49,12 @@ const mockQueryResult = (input: {
// Search across options via their de-accented labels.
const filteredOptions = options.filter(
(option) =>
filterRegex.test(deaccent(option.label)) || filterRegex.test(deaccent(option.heading)),
filterRegex.test(deaccent(option.label)) || filterRegex.test(deaccent(option.heading || '')),
) as unknown as AutocompleteSearchOrganizationEntry[]
return {
return nullableMock<AutocompleteSearchOrganizationQuery>({
autocompleteSearchOrganization: filteredOptions.slice(0, input.limit ?? 25),
}
})
}
const wrapperParameters = {

View File

@@ -12,7 +12,7 @@ import { convertToGraphQLId } from '#shared/graphql/utils.ts'
import type { ConfidentTake } from '#shared/types/utils.ts'
export const defaultOrganization = (): ConfidentTake<OrganizationQuery, 'organization'> =>
nullableMock({
nullableMock<ConfidentTake<OrganizationQuery, 'organization'>>({
__typename: 'Organization',
id: convertToGraphQLId('Organization', 100),
internalId: 100,

View File

@@ -4,10 +4,9 @@ import { reactive } from 'vue'
import type { FormSchemaField } from '#shared/components/Form/types.ts'
import { useOrganizationUpdateMutation } from '#shared/entities/organization/graphql/mutations/update.api.ts'
import type { EditableOrganization } from '#shared/entities/organization/types.ts'
import { defineFormSchema } from '#shared/form/defineFormSchema.ts'
import type { OrganizationQuery } from '#shared/graphql/types.ts'
import { EnumFormUpdaterId, EnumObjectManagerObjects } from '#shared/graphql/types.ts'
import type { ConfidentTake } from '#shared/types/utils.ts'
import { useDialogObjectForm } from '#mobile/components/CommonDialogObjectForm/useDialogObjectForm.ts'
@@ -36,9 +35,7 @@ export const useOrganizationEdit = () => {
{ showDirtyMark: true },
)
const openEditOrganizationDialog = async (
organization: ConfidentTake<OrganizationQuery, 'organization'>,
) => {
const openEditOrganizationDialog = async (organization: EditableOrganization) => {
const formChangeFields = reactive<Record<string, Partial<FormSchemaField>>>({
domain: {
required: !!organization.domainAssignment,

View File

@@ -3,6 +3,7 @@ import * as Types from '#shared/graphql/types.ts';
import gql from 'graphql-tag';
import { TicketAttributesFragmentDoc } from '../../../../../../shared/entities/ticket/graphql/fragments/ticketAttributes.api';
import { TicketMentionFragmentDoc } from '../../../../../../shared/entities/ticket/graphql/fragments/ticketMention.api';
import { ReferencingTicketFragmentDoc } from '../../../../../../shared/entities/ticket/graphql/fragments/referencingTicket.api';
import * as VueApolloComposable from '@vue/apollo-composable';
import * as VueCompositionApi from 'vue';
export type ReactiveFunction<TParam> = () => TParam;
@@ -11,6 +12,7 @@ export const TicketWithMentionLimitDocument = gql`
query ticketWithMentionLimit($ticketId: ID!, $mentionsCount: Int = null) {
ticket(ticketId: $ticketId) {
...ticketAttributes
aiSummaryEnabled
createArticleType {
id
name
@@ -24,10 +26,21 @@ export const TicketWithMentionLimitDocument = gql`
cursor
}
}
checklist {
id
completed
incomplete
total
complete
}
referencingChecklistTickets {
...referencingTicket
}
}
}
${TicketAttributesFragmentDoc}
${TicketMentionFragmentDoc}`;
${TicketMentionFragmentDoc}
${ReferencingTicketFragmentDoc}`;
export function useTicketWithMentionLimitQuery(variables: Types.TicketWithMentionLimitQueryVariables | VueCompositionApi.Ref<Types.TicketWithMentionLimitQueryVariables> | ReactiveFunction<Types.TicketWithMentionLimitQueryVariables>, options: VueApolloComposable.UseQueryOptions<Types.TicketWithMentionLimitQuery, Types.TicketWithMentionLimitQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<Types.TicketWithMentionLimitQuery, Types.TicketWithMentionLimitQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<Types.TicketWithMentionLimitQuery, Types.TicketWithMentionLimitQueryVariables>> = {}) {
return VueApolloComposable.useQuery<Types.TicketWithMentionLimitQuery, Types.TicketWithMentionLimitQueryVariables>(TicketWithMentionLimitDocument, variables, options);
}

View File

@@ -1,6 +1,7 @@
query ticketWithMentionLimit($ticketId: ID!, $mentionsCount: Int = null) {
ticket(ticketId: $ticketId) {
...ticketAttributes
aiSummaryEnabled
createArticleType {
id
name
@@ -14,5 +15,15 @@ query ticketWithMentionLimit($ticketId: ID!, $mentionsCount: Int = null) {
cursor
}
}
checklist {
id
completed
incomplete
total
complete
}
referencingChecklistTickets {
...referencingTicket
}
}
}

View File

@@ -3,11 +3,10 @@
import type { FormSchemaField } from '#shared/components/Form/types.ts'
import { useUserFormSchema } from '#shared/entities/user/composables/useUserFormSchema.ts'
import { useUserUpdateMutation } from '#shared/entities/user/graphql/mutations/update.api.ts'
import type { EditableUser } from '#shared/entities/user/types.ts'
import { defineFormSchema } from '#shared/form/defineFormSchema.ts'
import type { UserQuery } from '#shared/graphql/types.ts'
import { EnumFormUpdaterId, EnumObjectManagerObjects } from '#shared/graphql/types.ts'
import { useApplicationStore } from '#shared/stores/application.ts'
import type { ConfidentTake } from '#shared/types/utils.ts'
import { useDialogObjectForm } from '#mobile/components/CommonDialogObjectForm/useDialogObjectForm.ts'
@@ -29,7 +28,7 @@ export const useUserEdit = () => {
const application = useApplicationStore()
const openEditUserDialog = async (user: ConfidentTake<UserQuery, 'user'>) => {
const openEditUserDialog = async (user: EditableUser) => {
const formChangeFields: Record<string, Partial<FormSchemaField>> = {
note: {
props: {

View File

@@ -17,6 +17,7 @@ import { useLocaleStore } from '#shared/stores/locale.ts'
const locales: Record<string, LocalesQuery['locales'][number]> = {
de: {
__typename: 'Locale',
locale: 'de-de',
name: 'Deutsch',
dir: EnumTextDirection.Ltr,
@@ -24,6 +25,7 @@ const locales: Record<string, LocalesQuery['locales'][number]> = {
active: true,
},
ar: {
__typename: 'Locale',
locale: 'ar',
name: 'Arabic',
dir: EnumTextDirection.Rtl,

View File

@@ -13,6 +13,7 @@ import { LoginDocument } from '#shared/graphql/mutations/login.api.ts'
import { EnumTwoFactorAuthenticationMethod, type LoginMutation } from '#shared/graphql/types.ts'
const twoFactorAuthentication = () => ({
__typename: 'UserLoginTwoFactorMethods' as const,
availableTwoFactorAuthenticationMethods: [EnumTwoFactorAuthenticationMethod.AuthenticatorApp],
defaultTwoFactorAuthenticationMethod: EnumTwoFactorAuthenticationMethod.AuthenticatorApp,
recoveryCodesAvailable: false,
@@ -123,6 +124,7 @@ describe('two factor login flow', () => {
describe('show "try another method"', () => {
it.each([
{
__typename: 'UserLoginTwoFactorMethods' as const,
availableTwoFactorAuthenticationMethods: [
EnumTwoFactorAuthenticationMethod.AuthenticatorApp,
],
@@ -132,6 +134,7 @@ describe('two factor login flow', () => {
available: false,
},
{
__typename: 'UserLoginTwoFactorMethods' as const,
availableTwoFactorAuthenticationMethods: [
EnumTwoFactorAuthenticationMethod.AuthenticatorApp,
],

View File

@@ -12,12 +12,15 @@ import { TwoFactorMethodInitiateAuthenticationDocument } from '#shared/graphql/m
import { ApplicationConfigDocument } from '#shared/graphql/queries/applicationConfig.api.ts'
import { ConfigUpdatesDocument } from '#shared/graphql/subscriptions/configUpdates.api.ts'
import type { TwoFactorMethodInitiateAuthenticationMutation } from '#shared/graphql/types.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
import { createDeferred } from '#shared/utils/helpers.ts'
import LoginTwoFactor from '../LoginTwoFactor.vue'
const prepareInitialData = (
data: TwoFactorMethodInitiateAuthenticationMutation['twoFactorMethodInitiateAuthentication'],
data: DeepPartial<
TwoFactorMethodInitiateAuthenticationMutation['twoFactorMethodInitiateAuthentication']
>,
) => {
mockGraphQLApi(TwoFactorMethodInitiateAuthenticationDocument).willResolve({
twoFactorMethodInitiateAuthentication: {

View File

@@ -1,12 +1,20 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
import type { SearchQuery } from '#shared/graphql/types.ts'
import type { ConfidentTake } from '#shared/types/utils.ts'
import type { ConfidentTake, DeepPartial } from '#shared/types/utils.ts'
import { mockGraphQLApi } from '#tests/support/mock-graphql-api.ts'
import { nullableMock } from '#tests/support/utils.ts'
import { SearchDocument } from '../queries/searchOverview.api.ts'
export const mockSearchOverview = (search: ConfidentTake<SearchQuery, 'search'>) => {
return mockGraphQLApi(SearchDocument).willResolve({
search,
})
type SearchResult = ConfidentTake<SearchQuery, 'search'>
export const mockSearchOverview = (search: DeepPartial<SearchResult>) => {
return mockGraphQLApi(SearchDocument).willResolve(
nullableMock<SearchQuery>({
search: {
__typename: 'SearchResult',
...search,
},
}),
)
}

View File

@@ -10,6 +10,7 @@ export const SearchDocument = gql`
search(search: $search, onlyIn: $onlyIn, limit: $limit) {
totalCount
items {
__typename
... on Ticket {
id
internalId

View File

@@ -2,6 +2,7 @@ query search($search: String!, $onlyIn: EnumSearchableModels!, $limit: Int = 30)
search(search: $search, onlyIn: $onlyIn, limit: $limit) {
totalCount
items {
__typename
... on Ticket {
id
internalId

View File

@@ -8,6 +8,7 @@ import {
type TicketArticlesQuery,
} from '#shared/graphql/types.ts'
import { convertToGraphQLId } from '#shared/graphql/utils.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
import type { LastArrayElement } from 'type-fest'
@@ -23,9 +24,9 @@ type ArticleNode = LastArrayElement<TicketArticlesQuery['articles']['edges']>['n
export const articleContent = (
id: number,
mockedArticleData: Partial<ArticleNode>,
mockedArticleData: DeepPartial<ArticleNode>,
): ArticleNode => {
return {
return nullableMock<ArticleNode>({
__typename: 'TicketArticle',
id: convertToGraphQLId('TicketArticle', id),
internalId: id,
@@ -40,8 +41,13 @@ export const articleContent = (
firstname: 'John',
lastname: 'Doe',
fullname: 'John Doe',
email: 'john.doe@example.com',
active: true,
image: null,
vip: false,
outOfOffice: false,
outOfOfficeStartAt: null,
outOfOfficeEndAt: null,
authorizations: [],
},
internal: false,
@@ -53,28 +59,29 @@ export const articleContent = (
type: {
__typename: 'TicketArticleType',
name: 'article',
communication: false,
},
contentType: 'text/html',
attachmentsWithoutInline: [],
preferences: {},
...mockedArticleData,
}
})
}
export const mockArticleQuery = (
firstArticles: Partial<ArticleNode>,
articles: Partial<ArticleNode>[] = [],
firstArticles: DeepPartial<ArticleNode>,
articles: DeepPartial<ArticleNode>[] = [],
totalCount = articles.length + 1,
): TicketArticlesQuery => {
const articleNodes = articles.map((article, index) => {
return {
return nullableMock<TicketArticleEdge>({
__typename: 'TicketArticleEdge',
node: articleContent(article.internalId ?? index + 1, article),
cursor: `MI${index}`,
} as TicketArticleEdge
})
})
return nullableMock({
return nullableMock<TicketArticlesQuery>({
firstArticles: {
__typename: 'TicketArticleConnection',
edges: [

View File

@@ -140,7 +140,7 @@ const address = {
}
export const defaultArticles = (): TicketArticlesQuery =>
nullableMock({
nullableMock<TicketArticlesQuery>({
firstArticles: {
__typename: 'TicketArticleConnection',
edges: [

View File

@@ -340,6 +340,7 @@ describe('calling API to retry encryption', () => {
encryptionSuccess: false,
signingMessage: 'The certificate for verification could not be found.',
signingSuccess: false,
type: EnumSecurityStateType.Smime,
}
const { waitUntilTicketLoaded } = mockTicketDetailViewGql({
@@ -399,6 +400,7 @@ describe('calling API to retry encryption', () => {
encryptionSuccess: false,
signingMessage: 'The certificate for verification could not be found.',
signingSuccess: false,
type: EnumSecurityStateType.Smime,
}
const { waitUntilTicketLoaded } = mockTicketDetailViewGql({
@@ -459,9 +461,12 @@ describe('remote content removal', () => {
}
article.attachmentsWithoutInline = [
{
__typename: 'StoredFile',
id: convertToGraphQLId('Store', 1),
internalId: 1,
name: 'message',
size: null,
type: null,
preferences: {
'original-format': true,
},
@@ -951,6 +956,7 @@ it('correctly redirects from ticket hash-based routes with other ids', async ()
defaultArticles(),
{
articles: {
__typename: 'TicketArticleConnection',
edges: [],
pageInfo: {
endCursor: null,

View File

@@ -101,8 +101,10 @@ describe('static organization', () => {
...organization,
allMembers: {
...organization.allMembers,
__typename: 'UserConnection',
edges: organization.allMembers?.edges || [],
pageInfo: {
__typename: 'PageInfo',
endCursor: null,
},
totalCount: 2,

View File

@@ -8,7 +8,10 @@ import { mockPermissions } from '#tests/support/mock-permissions.ts'
import { nullableMock, waitUntil } from '#tests/support/utils.ts'
import { AutocompleteSearchUserDocument } from '#shared/components/Form/fields/FieldCustomer/graphql/queries/autocompleteSearch/user.api.ts'
import type { AutocompleteSearchUserQuery } from '#shared/graphql/types.ts'
import type {
AutocompleteSearchUserEntry,
AutocompleteSearchUserQuery,
} from '#shared/graphql/types.ts'
import { MutationHandler } from '#shared/server/apollo/handler/index.ts'
import { mockTicketObjectAttributesGql } from '#mobile/entities/ticket/__tests__/mocks/ticket-mocks.ts'
@@ -33,39 +36,40 @@ const mockUpdateMutationHandler = () => {
const defaultAutoCompleteSearchUserResult = (
hasSecondaryOrganizations = false,
): AutocompleteSearchUserQuery => ({
autocompleteSearchUser: [
nullableMock({
value: 2,
label: 'Nicole Braun',
labelPlaceholder: null,
heading: 'Zammad Foundation',
headingPlaceholder: null,
disabled: null,
icon: null,
user: {
id: 'gid://zammad/User/2',
internalId: 2,
firstname: 'Nicole',
lastname: 'Braun',
fullname: 'Nicole Braun',
image: null,
objectAttributeValues: [],
organization: {
id: 'gid://zammad/Organization/1',
internalId: 1,
name: 'Zammad Foundation',
active: true,
): AutocompleteSearchUserQuery =>
nullableMock<AutocompleteSearchUserQuery>({
autocompleteSearchUser: [
nullableMock<AutocompleteSearchUserEntry>({
value: 2,
label: 'Nicole Braun',
labelPlaceholder: null,
heading: 'Zammad Foundation',
headingPlaceholder: null,
disabled: null,
icon: null,
user: {
id: 'gid://zammad/User/2',
internalId: 2,
firstname: 'Nicole',
lastname: 'Braun',
fullname: 'Nicole Braun',
image: null,
objectAttributeValues: [],
__typename: 'Organization',
organization: {
id: 'gid://zammad/Organization/1',
internalId: 1,
name: 'Zammad Foundation',
active: true,
objectAttributeValues: [],
__typename: 'Organization',
},
hasSecondaryOrganizations,
__typename: 'User',
},
hasSecondaryOrganizations,
__typename: 'User',
},
__typename: 'AutocompleteSearchUserEntry',
}),
],
})
__typename: 'AutocompleteSearchUserEntry',
}),
],
})
const mockCustomerQueryResult = (autoCompleteSearchUserResult?: AutocompleteSearchUserQuery[]) => {
return mockGraphQLApi(AutocompleteSearchUserDocument).willResolve(
@@ -149,9 +153,9 @@ describe('TicketAction - change customer dialog', () => {
test('show organization field for customers with multiple organization (and also hide again)', async () => {
const mockCustomer = mockCustomerQueryResult([
defaultAutoCompleteSearchUserResult(true),
{
nullableMock<AutocompleteSearchUserQuery>({
autocompleteSearchUser: [
nullableMock({
nullableMock<AutocompleteSearchUserEntry>({
value: 200,
label: 'John Doe',
labelPlaceholder: null,
@@ -181,7 +185,7 @@ describe('TicketAction - change customer dialog', () => {
__typename: 'AutocompleteSearchUserEntry',
}),
],
},
}),
])
const view = renderComponent(TicketActionChangeCustomerDialog, {

View File

@@ -1,6 +1,7 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
import { renderComponent } from '#tests/support/components/index.ts'
import { nullableMock } from '#tests/support/utils.ts'
import type { TicketArticle } from '#shared/entities/ticket/types.ts'
import { convertToGraphQLId } from '#shared/graphql/utils.ts'
@@ -13,7 +14,7 @@ import ArticlesList from '../ArticlesList.vue'
it('renders delivery messages', () => {
const { ticket } = defaultTicket()
const articles: TicketArticle[] = [
{
nullableMock<TicketArticle>({
__typename: 'TicketArticle',
id: convertToGraphQLId('TicketArticle', 1),
internal: false,
@@ -30,7 +31,7 @@ it('renders delivery messages', () => {
__typename: 'User',
id: convertToGraphQLId('User', 1),
},
},
}),
]
const view = renderComponent(ArticlesList, {

View File

@@ -5,10 +5,12 @@ import { getAllByRole, waitFor } from '@testing-library/vue'
import { getByIconName } from '#tests/support/components/iconQueries.ts'
import { renderComponent } from '#tests/support/components/index.ts'
import { mockGraphQLApi } from '#tests/support/mock-graphql-api.ts'
import { nullableMock } from '#tests/support/utils.ts'
import { ObjectManagerFrontendAttributesDocument } from '#shared/entities/object-attributes/graphql/queries/objectManagerFrontendAttributes.api.ts'
import type { TicketArticle } from '#shared/entities/ticket/types.ts'
import { EnumSecurityStateType, type TicketArticleSecurityState } from '#shared/graphql/types.ts'
import { EnumSecurityStateType } from '#shared/graphql/types.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
import { ticketArticleObjectAttributes } from '#mobile/entities/ticket/__tests__/mocks/ticket-mocks.ts'
import { defaultArticles } from '#mobile/pages/ticket/__tests__/mocks/detail-view.ts'
@@ -17,6 +19,7 @@ import ArticleMetadataDialog from '../ArticleMetadataDialog.vue'
// parsed is tested in unit test
const getAddress = (raw: string) => ({
__typename: 'AddressesField' as const,
raw,
parsed: null,
})
@@ -43,7 +46,9 @@ describe('visuals for metadata', () => {
cc: getAddress('Joe Mike <joe.mike@zammad.org>'),
replyTo: getAddress('Arthur Miller <arthur.miller@zammad.org>'),
type: {
__typename: 'TicketArticleType',
name: 'email',
communication: true,
},
detectedLanguage: 'de',
createdAt,
@@ -114,14 +119,17 @@ describe('visuals for metadata', () => {
})
describe('rendering security field', () => {
const mockArticle = (security: TicketArticleSecurityState): TicketArticle => ({
...defaultArticles().firstArticles!.edges[0].node,
internalId: 1,
securityState: {
__typename: 'TicketArticleSecurityState',
...security,
},
})
const mockArticle = (
security: DeepPartial<NonNullable<TicketArticle['securityState']>>,
): TicketArticle =>
nullableMock<TicketArticle>({
...defaultArticles().firstArticles!.edges[0].node,
internalId: 1,
securityState: {
__typename: 'TicketArticleSecurityState',
...security,
},
})
describe('renders type', () => {
it('renders S/MIME type when provided', () => {

View File

@@ -22,6 +22,7 @@ import {
type ObjectManagerFrontendAttributesPayload,
type FormUpdaterQuery,
} from '#shared/graphql/types.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
import { FormUpdaterDocument } from '../../../graphql/queries/formUpdater.api.ts'
import additionalFrontendObjectAttributes from '../../mocks/additionalFrontendObjectAttributes.json'
@@ -207,7 +208,7 @@ const mergedObjectAttributes = mergeFrontendObjectAttributes(
)
const renderForm = async (
formUpdaterQueryResponse: FormUpdaterQuery | FormUpdaterQuery[],
formUpdaterQueryResponse: DeepPartial<FormUpdaterQuery> | DeepPartial<FormUpdaterQuery>[],
options: ExtendedMountingOptions<Props> = {},
objectManagerFrontendAttributes = mergedObjectAttributes,
) => {

View File

@@ -23,6 +23,7 @@ import {
type ObjectManagerFrontendAttributesPayload,
type FormUpdaterQuery,
} from '#shared/graphql/types.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
import { FormUpdaterDocument } from '../../../graphql/queries/formUpdater.api.ts'
import additionalFrontendObjectAttributes from '../../mocks/additionalFrontendObjectAttributes.json'
@@ -205,7 +206,7 @@ const mergedObjectAttributes = mergeFrontendObjectAttributes(
)
const renderForm = async (
formUpdaterQueryResponse: FormUpdaterQuery | FormUpdaterQuery[],
formUpdaterQueryResponse: DeepPartial<FormUpdaterQuery> | DeepPartial<FormUpdaterQuery>[],
options: ExtendedMountingOptions<Props> = {},
objectManagerFrontendAttributes = mergedObjectAttributes,
) => {

View File

@@ -15,6 +15,7 @@ export const AutocompleteSearchGenericDocument = gql`
headingPlaceholder
disabled
object {
__typename
... on User {
id
internalId
@@ -31,6 +32,7 @@ export const AutocompleteSearchGenericDocument = gql`
active
vip
organization {
__typename
id
internalId
name
@@ -52,6 +54,7 @@ export const AutocompleteSearchGenericDocument = gql`
allMembers(first: $membersCount) {
edges {
node {
__typename
id
internalId
login

View File

@@ -7,6 +7,7 @@ query autocompleteSearchGeneric($input: AutocompleteSearchGenericInput!, $member
headingPlaceholder
disabled
object {
__typename
... on User {
id
internalId
@@ -23,6 +24,7 @@ query autocompleteSearchGeneric($input: AutocompleteSearchGenericInput!, $member
active
vip
organization {
__typename
id
internalId
name
@@ -44,6 +46,7 @@ query autocompleteSearchGeneric($input: AutocompleteSearchGenericInput!, $member
allMembers(first: $membersCount) {
edges {
node {
__typename
id
internalId
login

View File

@@ -3,8 +3,9 @@
import { flushPromises } from '@vue/test-utils'
import { initializePiniaStore } from '#tests/support/components/renderComponent.ts'
import { nullableMock } from '#tests/support/utils.ts'
import type { Locale } from '#shared/graphql/types.ts'
import type { LocalesQuery } from '#shared/graphql/types.ts'
import { useLocaleStore } from '#shared/stores/locale.ts'
import { useDateFnsLocale, importDateFnsLocale } from '../useDateFnsLocale.ts'
@@ -33,9 +34,10 @@ describe('useDateFnsLocale', () => {
it('returns the correct date-fns locale based on the current locale store value', async () => {
const locale = useLocaleStore()
locale.localeData = {
locale.localeData = nullableMock<LocalesQuery['locales'][number]>({
__typename: 'Locale',
locale: 'en-gb',
} as Locale
})
const { dateFnsLocale } = useDateFnsLocale()
@@ -47,9 +49,10 @@ describe('useDateFnsLocale', () => {
it('returns the fallback en-US locale if the current locale is unsupported', async () => {
const locale = useLocaleStore()
locale.localeData = {
locale.localeData = nullableMock<LocalesQuery['locales'][number]>({
__typename: 'Locale',
locale: 'xx-xx',
} as Locale
})
const { dateFnsLocale } = useDateFnsLocale()

View File

@@ -278,8 +278,10 @@ describe('Fields - FieldFile', () => {
const file = new File([], 'foo.png', { type: 'image/png' })
const uploadedFileQuery: FormUploadCacheAddMutation = {
formUploadCacheAdd: {
__typename: 'FormUploadCacheAddPayload',
uploadedFiles: [
{
__typename: 'StoredFile',
id: '1',
name: file.name,
size: file.size,

View File

@@ -3,6 +3,7 @@
import { nullableMock } from '#tests/support/utils.ts'
import type { OnlineNotificationsQuery, Scalars } from '#shared/graphql/types.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
import type { LastArrayElement } from 'type-fest'
@@ -12,13 +13,14 @@ type OnlineNotificationNode = LastArrayElement<
export const mockOnlineNotification = (
id: Scalars['ID']['output'],
mockNotification: Partial<OnlineNotificationNode>,
mockNotification: DeepPartial<OnlineNotificationNode>,
): OnlineNotificationNode => {
return {
return nullableMock<OnlineNotificationNode>({
__typename: 'OnlineNotification',
seen: false,
createdAt: new Date().toISOString(),
createdBy: {
__typename: 'User',
id: '123',
fullname: 'Full Name',
lastname: 'Name',
@@ -26,6 +28,8 @@ export const mockOnlineNotification = (
email: 'email@example.org',
vip: false,
outOfOffice: false,
outOfOfficeStartAt: null,
outOfOfficeEndAt: null,
active: true,
image: null,
},
@@ -43,11 +47,11 @@ export const mockOnlineNotification = (
},
id,
...mockNotification,
}
})
}
export const mockOnlineNotificationQuery = (
mockNotifications: Array<Partial<OnlineNotificationNode>>,
mockNotifications: Array<DeepPartial<OnlineNotificationNode>>,
): OnlineNotificationsQuery => {
const edges = mockNotifications.map((item, index) => {
const id = index + 1

View File

@@ -32,6 +32,7 @@ export const OnlineNotificationsDocument = gql`
createdByAi
}
metaObject {
__typename
... on Ticket {
id
internalId
@@ -55,6 +56,7 @@ export const OnlineNotificationsDocument = gql`
id
internalId
data {
__typename
... on OnlineNotificationStandaloneBulkJobData {
total
failedCount

View File

@@ -24,6 +24,7 @@ query onlineNotifications {
createdByAi
}
metaObject {
__typename
... on Ticket {
id
internalId
@@ -47,6 +48,7 @@ query onlineNotifications {
id
internalId
data {
__typename
... on OnlineNotificationStandaloneBulkJobData {
total
failedCount

View File

@@ -0,0 +1,8 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
import type { ObjectLike } from '#shared/types/utils.ts'
export interface EditableOrganization extends ObjectLike {
id: string
domainAssignment?: boolean | null
}

View File

@@ -4,28 +4,34 @@ import { nullableMock } from '#tests/support/utils.ts'
import { EnumTicketArticleSenderName, type TicketArticlesQuery } from '#shared/graphql/types.ts'
import { convertToGraphQLId } from '#shared/graphql/utils.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
import type { LastArrayElement } from 'type-fest'
export const mockTicketCreateAtDate = new Date(2011, 11, 11, 11, 11, 11, 11)
export const defaultAuthor = {
__typename: 'User',
__typename: 'User' as const,
id: '11',
firstname: 'Author',
lastname: 'Joe',
fullname: 'Author Joe',
email: 'author.joe@example.com',
active: true,
image: null,
vip: false,
outOfOffice: false,
outOfOfficeStartAt: null,
outOfOfficeEndAt: null,
authorizations: [],
}
export const defaultFromAddress = {
__typename: 'AddressesField',
__typename: 'AddressesField' as const,
raw: 'Nicole Braun <nicole.braun@zammad.org>',
parsed: [
{
__typename: 'EmailAddressParsed',
__typename: 'EmailAddressParsed' as const,
name: 'Nicole Braun',
emailAddress: 'nicole.braun@zammad.org',
isSystemAddress: false,
@@ -36,27 +42,28 @@ export const defaultFromAddress = {
export const defaultBodyWithUrls = '<p>Default test body</p>'
type ArticleNode = LastArrayElement<TicketArticlesQuery['articles']['edges']>['node']
type ArticleNodeOptions = DeepPartial<ArticleNode>
export const createDummyArticle = (options?: {
articleId?: number
from?: ArticleNode['from']
author?: ArticleNode['author']
internal?: ArticleNode['internal']
bodyWithUrls?: ArticleNode['bodyWithUrls']
to?: ArticleNode['to']
cc?: ArticleNode['cc']
replyTo?: ArticleNode['replyTo']
subject?: ArticleNode['subject']
from?: ArticleNodeOptions['from']
author?: ArticleNodeOptions['author']
internal?: ArticleNodeOptions['internal']
bodyWithUrls?: ArticleNodeOptions['bodyWithUrls']
to?: ArticleNodeOptions['to']
cc?: ArticleNodeOptions['cc']
replyTo?: ArticleNodeOptions['replyTo']
subject?: ArticleNodeOptions['subject']
articleType?: string
attachmentsWithoutInline?: ArticleNode['attachmentsWithoutInline']
contentType?: ArticleNode['contentType']
securityState?: ArticleNode['securityState']
attachmentsWithoutInline?: ArticleNodeOptions['attachmentsWithoutInline']
contentType?: ArticleNodeOptions['contentType']
securityState?: ArticleNodeOptions['securityState']
senderName?: EnumTicketArticleSenderName
mediaErrorState?: ArticleNode['mediaErrorState']
preferences?: ArticleNode['preferences']
detectedLanguage?: ArticleNode['detectedLanguage']
mediaErrorState?: ArticleNodeOptions['mediaErrorState']
preferences?: ArticleNodeOptions['preferences']
detectedLanguage?: ArticleNodeOptions['detectedLanguage']
}) => {
return nullableMock({
return nullableMock<ArticleNode>({
__typename: 'TicketArticle',
id: convertToGraphQLId('TicketArticle', options?.articleId || 1),
internalId: options?.articleId || 1,
@@ -68,7 +75,6 @@ export const createDummyArticle = (options?: {
replyTo: options?.replyTo || null,
messageIdMd5: null,
contentType: options?.contentType || 'text/plain',
references: null,
attachmentsWithoutInline: options?.attachmentsWithoutInline || [],
preferences: options?.preferences || {},
bodyWithUrls: options?.bodyWithUrls || defaultBodyWithUrls,
@@ -78,6 +84,7 @@ export const createDummyArticle = (options?: {
type: {
__typename: 'TicketArticleType',
name: options?.articleType || 'string',
communication: false,
},
sender: {
__typename: 'TicketArticleSender',
@@ -86,5 +93,5 @@ export const createDummyArticle = (options?: {
securityState: options?.securityState === undefined ? null : options.securityState,
mediaErrorState: options?.mediaErrorState === undefined ? null : options.mediaErrorState,
detectedLanguage: options?.detectedLanguage ?? null,
}) as ArticleNode
})
}

View File

@@ -5,6 +5,7 @@ import { nullableMock } from '#tests/support/utils.ts'
import type { TicketQuery } from '#shared/graphql/types.ts'
import { EnumTicketStateColorCode, EnumTicketSummaryGeneration } from '#shared/graphql/types.ts'
import { convertToGraphQLId } from '#shared/graphql/utils.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
export const mockTicketCreateDate = new Date(2011, 11, 11, 11, 11, 11, 11)
export const mockTicketUpdateDate = new Date(2011, 12, 12, 12, 12, 12, 12)
@@ -89,7 +90,7 @@ export const defaultPolicy = {
}
export const defaultMentions = {
__typename: 'Mentions',
__typename: 'MentionConnection',
totalCount: 0,
edges: [],
}
@@ -100,25 +101,25 @@ export const defaultMentions = {
* * */
export const createDummyTicket = <R = TicketQuery['ticket']>(options?: {
ticketId?: string
owner?: TicketQuery['ticket']['owner']
customer?: TicketQuery['ticket']['customer']
organization?: TicketQuery['ticket']['organization']
state?: TicketQuery['ticket']['state']
owner?: DeepPartial<TicketQuery['ticket']['owner']>
customer?: DeepPartial<TicketQuery['ticket']['customer']>
organization?: DeepPartial<TicketQuery['ticket']['organization']>
state?: DeepPartial<TicketQuery['ticket']['state']>
articleType?: string
group?: Partial<TicketQuery['ticket']['group']>
defaultPriority?: TicketQuery['ticket']['priority']
defaultPolicy?: TicketQuery['ticket']['policy']
mentions?: TicketQuery['ticket']['mentions']
group?: DeepPartial<TicketQuery['ticket']['group']>
defaultPriority?: DeepPartial<TicketQuery['ticket']['priority']>
defaultPolicy?: DeepPartial<TicketQuery['ticket']['policy']>
mentions?: DeepPartial<TicketQuery['ticket']['mentions']>
subscribed?: TicketQuery['ticket']['subscribed']
colorCode?: EnumTicketStateColorCode
title?: TicketQuery['ticket']['title']
number?: TicketQuery['ticket']['number']
checklist?: TicketQuery['ticket']['checklist']
referencingChecklistTickets?: TicketQuery['ticket']['referencingChecklistTickets']
checklist?: DeepPartial<TicketQuery['ticket']['checklist']>
referencingChecklistTickets?: DeepPartial<TicketQuery['ticket']['referencingChecklistTickets']>
timeUnit?: TicketQuery['ticket']['timeUnit']
timeUnitsPerType?: TicketQuery['ticket']['timeUnitsPerType']
timeUnitsPerType?: DeepPartial<TicketQuery['ticket']['timeUnitsPerType']>
tags?: string[]
externalReferences?: TicketQuery['ticket']['externalReferences']
externalReferences?: DeepPartial<TicketQuery['ticket']['externalReferences']>
preferences?: TicketQuery['ticket']['preferences']
sharedDraftZoomId?: number
aiAgentRunning?: TicketQuery['ticket']['aiAgentRunning']
@@ -127,7 +128,7 @@ export const createDummyTicket = <R = TicketQuery['ticket']>(options?: {
firstResponseEscalationAt?: TicketQuery['ticket']['firstResponseEscalationAt']
updateEscalationAt?: TicketQuery['ticket']['updateEscalationAt']
closeEscalationAt?: TicketQuery['ticket']['closeEscalationAt']
objectAttributeValues?: TicketQuery['ticket']['objectAttributeValues']
objectAttributeValues?: DeepPartial<TicketQuery['ticket']['objectAttributeValues']>
}): R => {
return nullableMock({
__typename: 'Ticket',

View File

@@ -3,7 +3,7 @@
import { mockApplicationConfig } from '#tests/support/mock-applicationConfig.ts'
import { setupView } from '#tests/support/mock-user.ts'
import { type TicketView } from '#shared/entities/ticket/types.ts'
import { type TicketArticle, type TicketView } from '#shared/entities/ticket/types.ts'
import { createTicketArticle, createTestArticleActions, createTicket } from './utils.ts'
@@ -51,7 +51,7 @@ describe('article permanent link action', () => {
const articlePermalink = actions.find((action) => action.name === 'article-permalink')
articlePermalink?.perform?.(ticket, article, {} as TicketArticlePerformOptions)
articlePermalink?.perform?.(ticket, article as TicketArticle, {} as TicketArticlePerformOptions)
expect(copyToClipboardMock).toHaveBeenCalledWith(
`https://example.com/desktop/tickets/${ticket.internalId}/${article.internalId}`,

View File

@@ -1,23 +1,31 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
import { generateObjectData } from '#tests/graphql/builders/index.ts'
import { nullableMock } from '#tests/support/utils.ts'
import type { TicketArticle } from '#shared/graphql/types.ts'
import type { TicketArticle } from '#shared/entities/ticket/types.ts'
import type { TicketArticleEmailForwardReplyMutation } from '#shared/graphql/types.ts'
import type { ConfidentTake } from '#shared/types/utils.ts'
import { buildEmailForwardHeader } from '../email/forward.ts'
type ForwardReplyPayload = ConfidentTake<
TicketArticleEmailForwardReplyMutation,
'ticketArticleEmailForwardReply'
>
describe('building header, when "forward" action is called', () => {
it('renders all fields', () => {
const article = generateObjectData<TicketArticle>('TicketArticle')
article.subject = 'Article Subject'
article.createdAt = new Date(2020, 1, 1).toISOString()
const meta = {
const meta = nullableMock<ForwardReplyPayload>({
quotableFrom: 'Jhon Doe <jhon.doe@email.dcom>',
quotableCc: 'Agent Rodrigez',
quotableTo: 'Agent Smith <smith.a@matrix.com>',
attachments: [],
}
})
expect(buildEmailForwardHeader(article, meta)).toBe(
'<p>Subject: Article Subject<br>Date: 2020-02-01 00:00<br>From: Jhon Doe &lt;jhon.doe@email.dcom&gt;<br>To: Agent Smith &lt;smith.a@matrix.com&gt;<br>CC: Agent Rodrigez<br><br></p>',
@@ -29,12 +37,12 @@ describe('building header, when "forward" action is called', () => {
article.subject = null
article.createdAt = new Date(2020, 1, 1).toISOString()
const meta = {
const meta = nullableMock<ForwardReplyPayload>({
quotableFrom: 'Jhon Doe <jhon.doe@email.dcom>',
quotableCc: null,
quotableTo: 'Agent Smith <smith.a@matrix.com>',
attachments: [],
}
})
expect(buildEmailForwardHeader(article, meta)).toBe(
'<p>Date: 2020-02-01 00:00<br>From: Jhon Doe &lt;jhon.doe@email.dcom&gt;<br>To: Agent Smith &lt;smith.a@matrix.com&gt;<br><br></p>',

View File

@@ -4,11 +4,7 @@ import { generateObjectData } from '#tests/graphql/builders/index.ts'
import { initializeStore } from '#tests/support/components/initializeStore.ts'
import type { TicketArticle, TicketById } from '#shared/entities/ticket/types.ts'
import {
EnumTicketArticleSenderName,
type PolicyTicket,
type Ticket,
} from '#shared/graphql/types.ts'
import { EnumTicketArticleSenderName, type PolicyTicket } from '#shared/graphql/types.ts'
import type { AppName } from '#shared/types/app.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
@@ -16,8 +12,10 @@ import { createArticleActions, createArticleTypes } from '../index.ts'
import type { TicketActionAddOptions } from '../types.ts'
export const createTicketArticle = (defaults?: DeepPartial<TicketArticle>) => {
return generateObjectData<TicketArticle>('TicketArticle', defaults)
type TestTicketArticle = DeepPartial<TicketArticle> & Pick<TicketArticle, 'id' | 'internalId'>
export const createTicketArticle = (defaults?: DeepPartial<TicketArticle>): TestTicketArticle => {
return generateObjectData<TicketArticle>('TicketArticle', defaults) as TestTicketArticle
}
const defaultOptions: Pick<TicketActionAddOptions, 'onDispose' | 'recalculate'> = {
@@ -25,8 +23,8 @@ const defaultOptions: Pick<TicketActionAddOptions, 'onDispose' | 'recalculate'>
onDispose: vi.fn(),
}
export const createTicket = (defaults?: DeepPartial<Ticket>) =>
generateObjectData<Ticket>('Ticket', defaults)
export const createTicket = (defaults?: DeepPartial<TicketById>): TicketById =>
generateObjectData<TicketById>('Ticket', defaults)
export const createEligibleTicketArticleReplyData = (
type: string,
@@ -48,14 +46,14 @@ export const createEligibleTicketArticleReplyData = (
export const createTestArticleActions = (
ticket: TicketById,
article: TicketArticle,
article: TestTicketArticle,
app: AppName = 'mobile',
) => {
initializeStore()
return createArticleActions(ticket, article, app, defaultOptions)
return createArticleActions(ticket, article as TicketArticle, app, defaultOptions)
}
export const createTestArticleTypes = (ticket: TicketById, app: AppName = 'mobile') => {
initializeStore()
return createArticleTypes(ticket, app)
return createArticleTypes(ticket as TicketById, app)
}

View File

@@ -7,7 +7,7 @@
// PolicyTicket,
import { generateObjectData } from '#tests/graphql/builders/index.ts'
import type { Ticket } from '#shared/graphql/types.ts'
import type { TicketById } from '#shared/entities/ticket/types.ts'
// import type { AppName } from '#shared/types/app.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
@@ -27,8 +27,8 @@ import type { DeepPartial } from '#shared/types/utils.ts'
// onDispose: vi.fn(),
// }
export const createTicket = (defaults?: DeepPartial<Ticket>) =>
generateObjectData<Ticket>('Ticket', defaults)
export const createTicket = (defaults?: DeepPartial<TicketById>): TicketById =>
generateObjectData<TicketById>('Ticket', defaults)
// export const createEligibleTicketArticleReplyData = (
// type: string,

View File

@@ -5,12 +5,12 @@ import { ref } from 'vue'
import { generateObjectData } from '#tests/graphql/builders/index.ts'
import { mockPermissions } from '#tests/support/mock-permissions.ts'
import type { Ticket } from '#shared/graphql/types.ts'
import type { TicketById } from '#shared/entities/ticket/types.ts'
import { useTicketView } from '../useTicketView.ts'
const ticketDefault = generateObjectData<Ticket>('Ticket')
const ticket = ref<Ticket | undefined>(ticketDefault)
const ticketDefault = generateObjectData<TicketById>('Ticket')
const ticket = ref<TicketById | undefined>(ticketDefault)
describe('useTicketView', () => {
it('check agent permission', () => {

View File

@@ -4,17 +4,20 @@ import { computed, toRef, type Ref } from 'vue'
import { useApplicationStore } from '#shared/stores/application.ts'
import type { TicketById } from '../types.ts'
import type { TicketLabel } from '../types.ts'
export const useTicketNumberAndTitle = (ticket?: Ref<Partial<TicketById> | undefined>) => {
export const useTicketNumberAndTitle = (ticket?: Ref<TicketLabel | undefined>) => {
const config = toRef(useApplicationStore(), 'config')
const getTicketNumberWithHook = (ticketNumber?: number | string) => {
const getTicketNumberWithHook = (ticketNumber?: number | string | null) => {
if (!ticketNumber) return ''
return `${config.value.ticket_hook}${ticketNumber}`
}
const getTicketNumberWithTitle = (ticketNumber?: number | string, ticketTitle?: string) => {
const getTicketNumberWithTitle = (
ticketNumber?: number | string | null,
ticketTitle?: string | null,
) => {
if (!ticketNumber || !ticketTitle) return ''
return `${getTicketNumberWithHook(ticketNumber)} - ${ticketTitle}`
}

View File

@@ -5,12 +5,14 @@ import type { SecurityValue } from '#shared/components/Form/fields/FieldSecurity
import type { FormFieldValue } from '#shared/components/Form/types.ts'
import type { TicketArticleFormValues } from '#shared/entities/ticket-article/action/plugins/types.ts'
import {
type TicketQuery,
type EnumSecurityOption,
type EnumTaskbarApp,
type EnumTicketStateColorCode,
type ReferencingTicketFragment,
type TicketArticlesQuery,
type TicketLiveUser,
type TicketQuery,
type TicketsCachedByOverviewQuery,
type EnumTaskbarApp,
type EnumSecurityOption,
} from '#shared/graphql/types.ts'
import type { ConfidentTake, PartialRequired } from '#shared/types/utils.ts'
@@ -45,6 +47,20 @@ export type TicketByList = NonNullable<
export type TicketArticle = ConfidentTake<TicketArticlesQuery, 'articles.edges.node'>
export interface TicketLabel {
id?: string
internalId?: number
number?: number | string | null
title?: string | null
createdAt?: string | null
stateColorCode?: EnumTicketStateColorCode | null
state?: {
name?: string | null
} | null
}
export type ReferencingTicket = ReferencingTicketFragment
export interface TicketCustomerUpdateFormData {
customer_id: number
organization_id?: number

View File

@@ -1,8 +1,17 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
import type { ObjectLike } from '#shared/types/utils.ts'
export interface SignupFormData {
firstname: string
lastname: string
email: string
password: string
}
export interface EditableUser extends ObjectLike {
id: string
organization?: {
internalId?: number | null
} | null
}

View File

@@ -3,6 +3,7 @@ import * as Types from '#shared/graphql/types.ts';
import gql from 'graphql-tag';
export const HistoryIssuerFragmentDoc = gql`
fragment HistoryIssuer on HistoryRecordIssuer {
__typename
... on User {
id
internalId
@@ -41,6 +42,7 @@ export const HistoryIssuerFragmentDoc = gql`
`;
export const HistoryEventObjectFragmentDoc = gql`
fragment HistoryEventObject on HistoryRecordEventObject {
__typename
... on Checklist {
id
name

View File

@@ -4,6 +4,7 @@
# app/frontend/shared/server/apollo/cache/initializer/history.ts
fragment HistoryIssuer on HistoryRecordIssuer {
__typename
... on User {
id
internalId
@@ -41,6 +42,7 @@ fragment HistoryIssuer on HistoryRecordIssuer {
}
fragment HistoryEventObject on HistoryRecordEventObject {
__typename
... on Checklist {
id
name

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -17,6 +17,7 @@ describe('localeFinder', () => {
const locales = [
{
__typename: 'Locale' as const,
active: true,
alias: 'de',
dir: EnumTextDirection.Ltr,
@@ -24,6 +25,7 @@ describe('localeFinder', () => {
name: 'Deutsch',
},
{
__typename: 'Locale' as const,
active: true,
alias: 'es',
dir: EnumTextDirection.Ltr,
@@ -31,6 +33,7 @@ describe('localeFinder', () => {
name: 'Español',
},
{
__typename: 'Locale' as const,
active: true,
alias: '',
dir: EnumTextDirection.Ltr,
@@ -52,6 +55,7 @@ describe('localeFinder', () => {
it('returns default locale for no match', () => {
windowSpy.mockImplementation(() => ['sv-SV'])
expect(localeForBrowserLanguage(locales)).toStrictEqual({
__typename: 'Locale',
active: true,
alias: 'en',
dir: EnumTextDirection.Ltr,

View File

@@ -23,6 +23,7 @@ const localeForBrowserLanguage = (
}
return {
__typename: 'Locale',
locale: 'en-us',
alias: 'en',
// eslint-disable-next-line zammad/zammad-detect-translatable-string

View File

@@ -2,9 +2,12 @@
import { createPinia, setActivePinia } from 'pinia'
import { nullableMock } from '#tests/support/utils.ts'
import { errorOptions } from '#shared/router/error.ts'
import { useAuthenticationStore } from '#shared/stores/authentication.ts'
import { useSessionStore } from '#shared/stores/session.ts'
import type { UserData } from '#shared/types/store.ts'
import permissionGuard from '../permission.ts'
@@ -60,14 +63,16 @@ describe('permissionGuard', () => {
} as RouteLocationNormalized
useAuthenticationStore().authenticated = true
useSessionStore().user = {
useSessionStore().user = nullableMock<UserData>({
__typename: 'User',
id: '123',
internalId: 1,
permissions: {
__typename: 'UserPermission',
names: ['example.view'],
},
objectAttributeValues: [],
}
})
const result = permissionGuard(to, from, vi.fn())
@@ -99,14 +104,16 @@ describe('permissionGuard', () => {
} as RouteLocationNormalized
useAuthenticationStore().authenticated = true
useSessionStore().user = {
useSessionStore().user = nullableMock<UserData>({
__typename: 'User',
id: '123',
internalId: 1,
permissions: {
__typename: 'UserPermission',
names: ['ticket.agent'],
},
objectAttributeValues: [],
}
})
const result = permissionGuard(to, from, vi.fn())

View File

@@ -14,6 +14,7 @@ const mockQueryResult = (): LocalesQuery => {
return {
locales: [
{
__typename: 'Locale',
locale: 'de-de',
name: 'Deutsch',
dir: EnumTextDirection.Ltr,
@@ -21,6 +22,7 @@ const mockQueryResult = (): LocalesQuery => {
active: true,
},
{
__typename: 'Locale',
locale: 'ar',
name: 'Arabic',
dir: EnumTextDirection.Rtl,

View File

@@ -164,7 +164,10 @@ export const useSessionStore = defineStore(
const setUserPreference = (key: string, value: JsonValue) => {
if (!user.value) return
user.value.preferences[key] = value
user.value.preferences = {
...user.value.preferences,
[key]: value,
}
return user.value
}

View File

@@ -49,11 +49,12 @@ export type OperationMutationOptions<TResult, TVariables> =
| ReactiveFunction<UseMutationOptions<TResult, TVariables>>
export type OperationMutationFunction<
TResult = OperationMutationResult,
// oxlint-disable-next-line no-explicit-any
TVariables = any,
TResult = any,
// oxlint-disable-next-line no-explicit-any
TVariables extends OperationVariables = any,
> = (
options: OperationMutationOptions<TResult, TVariables>,
options?: OperationMutationOptions<TResult, TVariables>,
) => UseMutationReturn<TResult, TVariables>
export type OperationQueryFunction<

View File

@@ -1,29 +1,36 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
import { useSessionStore } from '#shared/stores/session.ts'
import type { UserData } from '#shared/types/store.ts'
import { initializeStore } from './components/initializeStore.ts'
import { nullableMock } from './utils.ts'
export const mockPermissions = (permissions: string[]) => {
initializeStore()
const session = useSessionStore()
if (!session.user) {
session.user = {
session.user = nullableMock({
__typename: 'User',
id: '123',
internalId: 1,
objectAttributeValues: [],
}
permissions: {
__typename: 'UserPermission',
names: permissions,
},
}) as unknown as UserData
}
session.user!.permissions = { names: permissions }
session.user!.permissions = { __typename: 'UserPermission', names: permissions }
if (Symbol.for('tests.permissions') in globalThis) return
Object.defineProperty(globalThis, Symbol.for('tests.permissions'), {
get() {
const session = useSessionStore()
return session.user?.permissions || { names: [] }
return session.user?.permissions || { __typename: 'UserPermission', names: [] }
},
configurable: true,
})

View File

@@ -3,17 +3,21 @@
import { convertToGraphQLId } from '#shared/graphql/utils.ts'
import { useSessionStore } from '#shared/stores/session.ts'
import type { UserData } from '#shared/types/store.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
import { initializePiniaStore } from './components/renderComponent.ts'
import { nullableMock } from './utils.ts'
export const mockUserCurrent = (mockUser: Partial<UserData> = {}) => {
export const mockUserCurrent = (mockUser: DeepPartial<UserData> = {}) => {
initializePiniaStore()
const session = useSessionStore()
session.user = {
session.user = nullableMock({
__typename: 'User',
id: convertToGraphQLId('User', 2),
internalId: 2,
objectAttributeValues: [],
preferences: {},
permissions: null,
...mockUser,
}
}) as UserData
}

View File

@@ -2,13 +2,14 @@
import { FormUpdaterDocument } from '#shared/components/Form/graphql/queries/formUpdater.api.ts'
import type { FormUpdaterQuery } from '#shared/graphql/types.ts'
import type { DeepPartial } from '#shared/types/utils.ts'
import { mockGraphQLApi } from '../mock-graphql-api.ts'
export const mockFormUpdater = (formUpdater?: FormUpdaterQuery) => {
export const mockFormUpdater = (formUpdater?: DeepPartial<FormUpdaterQuery>) => {
return mockGraphQLApi(FormUpdaterDocument).willResolve(
formUpdater || {
formUpdater: {},
formUpdater: { __typename: 'FormUpdaterResult' },
},
)
}

View File

@@ -9,6 +9,12 @@ import { TicketOverviewUpdatesDocument } from '#mobile/entities/ticket/graphql/s
import { mockGraphQLApi, mockGraphQLSubscription } from '../mock-graphql-api.ts'
const column = (key: string, value: string) => ({
__typename: 'KeyValue' as const,
key,
value,
})
export const getApiTicketOverviews = (): TicketOverviewsQuery => ({
ticketOverviews: [
{
@@ -25,17 +31,17 @@ export const getApiTicketOverviews = (): TicketOverviewsQuery => ({
prio: 100,
active: true,
viewColumns: [
{ key: 'number', value: 'Number' },
{ key: 'title', value: 'Title' },
{ key: 'created_at', value: 'Created at' },
{ key: 'updated_at', value: 'Updated at' },
{ key: 'priority', value: 'Priority' },
column('number', 'Number'),
column('title', 'Title'),
column('created_at', 'Created at'),
column('updated_at', 'Updated at'),
column('priority', 'Priority'),
],
orderColumns: [
{ key: 'number', value: 'Number' },
{ key: 'title', value: 'Title' },
{ key: 'created_at', value: 'Created at' },
{ key: 'updated_at', value: 'Updated at' },
column('number', 'Number'),
column('title', 'Title'),
column('created_at', 'Created at'),
column('updated_at', 'Updated at'),
],
groupBy: null,
},
@@ -53,16 +59,16 @@ export const getApiTicketOverviews = (): TicketOverviewsQuery => ({
prio: 200,
active: true,
viewColumns: [
{ key: 'number', value: 'Number' },
{ key: 'title', value: 'Title' },
{ key: 'created_at', value: 'Created at' },
{ key: 'updated_at', value: 'Updated at' },
column('number', 'Number'),
column('title', 'Title'),
column('created_at', 'Created at'),
column('updated_at', 'Updated at'),
],
orderColumns: [
{ key: 'number', value: 'Number' },
{ key: 'title', value: 'Title' },
{ key: 'created_at', value: 'Created at' },
{ key: 'updated_at', value: 'Updated at' },
column('number', 'Number'),
column('title', 'Title'),
column('created_at', 'Created at'),
column('updated_at', 'Updated at'),
],
groupBy: null,
},
@@ -80,16 +86,16 @@ export const getApiTicketOverviews = (): TicketOverviewsQuery => ({
prio: 300,
active: true,
viewColumns: [
{ key: 'number', value: 'Number' },
{ key: 'title', value: 'Title' },
{ key: 'created_at', value: 'Created at' },
{ key: 'updated_at', value: 'Updated at' },
column('number', 'Number'),
column('title', 'Title'),
column('created_at', 'Created at'),
column('updated_at', 'Updated at'),
],
orderColumns: [
{ key: 'number', value: 'Number' },
{ key: 'title', value: 'Title' },
{ key: 'created_at', value: 'Created at' },
{ key: 'updated_at', value: 'Updated at' },
column('number', 'Number'),
column('title', 'Title'),
column('created_at', 'Created at'),
column('updated_at', 'Updated at'),
],
groupBy: null,
},

Some files were not shown because too many files have changed in this diff Show More