ObjectAttributes.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. // Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
  2. import { getByRole } from '@testing-library/vue'
  3. import { flushPromises } from '@vue/test-utils'
  4. import { keyBy } from 'lodash-es'
  5. import { renderComponent } from '#tests/support/components/index.ts'
  6. import { mockApplicationConfig } from '#tests/support/mock-applicationConfig.ts'
  7. import { mockPermissions } from '#tests/support/mock-permissions.ts'
  8. import { i18n } from '#shared/i18n.ts'
  9. import ObjectAttributes from '../ObjectAttributes.vue'
  10. import attributes from './attributes.json'
  11. vi.hoisted(() => {
  12. vi.setSystemTime('2021-04-09T10:11:12Z')
  13. })
  14. const attributesByKey = keyBy(attributes, 'name')
  15. describe('common object attributes interface', () => {
  16. beforeEach(() => {
  17. mockApplicationConfig({
  18. pretty_date_format: 'absolute',
  19. })
  20. })
  21. test('renders all available attributes', () => {
  22. mockPermissions(['admin.user', 'ticket.agent'])
  23. const object = {
  24. login: 'some_object',
  25. address: 'Berlin, Street, House',
  26. vip: true,
  27. note: 'note',
  28. active: true,
  29. objectAttributeValues: [
  30. {
  31. attribute: attributesByKey.date_attribute,
  32. value: '2022-08-19',
  33. __typename: 'ObjectAttributeValue',
  34. },
  35. {
  36. attribute: attributesByKey.textarea_field,
  37. value: 'textarea text',
  38. },
  39. {
  40. attribute: attributesByKey.integer_field,
  41. value: 600,
  42. },
  43. {
  44. attribute: attributesByKey.date_time_field,
  45. value: '2022-08-11T05:00:00.000Z',
  46. },
  47. {
  48. attribute: attributesByKey.single_select,
  49. value: 'key1',
  50. },
  51. {
  52. attribute: attributesByKey.multi_select_field,
  53. value: ['key1', 'key2'],
  54. },
  55. {
  56. attribute: attributesByKey.single_tree_select,
  57. value: 'key1::key1_child1',
  58. },
  59. {
  60. attribute: attributesByKey.multi_tree_select,
  61. value: ['key1', 'key2', 'key2::key2_child1'],
  62. },
  63. {
  64. attribute: attributesByKey.some_url,
  65. value: 'https://url.com',
  66. },
  67. {
  68. attribute: attributesByKey.some_email,
  69. value: 'email@email.com',
  70. },
  71. {
  72. attribute: attributesByKey.phone,
  73. value: '+49 123456789',
  74. },
  75. {
  76. attribute: attributesByKey.external_attribute,
  77. value: { value: 1, label: 'Display External' },
  78. },
  79. ],
  80. }
  81. i18n.setTranslationMap(
  82. new Map([
  83. ['FORMAT_DATE', 'dd/mm/yyyy'],
  84. ['FORMAT_DATETIME', 'dd/mm/yyyy HH:MM'],
  85. ]),
  86. )
  87. const view = renderComponent(ObjectAttributes, {
  88. props: {
  89. object,
  90. attributes,
  91. },
  92. router: true,
  93. store: true,
  94. })
  95. const getRegion = (name: string) => view.getByRole('region', { name })
  96. expect(getRegion('Login')).toHaveTextContent(object.login)
  97. expect(getRegion('Address')).toHaveTextContent(object.address)
  98. expect(getRegion('VIP')).toHaveTextContent('yes')
  99. expect(getRegion('Note')).toHaveTextContent(object.note)
  100. expect(getRegion('Active')).toHaveTextContent('yes')
  101. expect(getRegion('Date Attribute')).toHaveTextContent(/19\/08\/2022$/)
  102. expect(getRegion('Textarea Field')).toHaveTextContent('textarea text')
  103. expect(getRegion('Integer Field')).toHaveTextContent('600')
  104. expect(getRegion('DateTime Field')).toHaveTextContent('11/08/2022 05:00')
  105. expect(getRegion('Single Select Field')).toHaveTextContent('Display1')
  106. expect(getRegion('Multi Select Field')).toHaveTextContent(
  107. 'Display1, Display2',
  108. )
  109. expect(getRegion('Single Tree Select Field')).toHaveTextContent(
  110. 'key1::key1_child1',
  111. )
  112. expect(getRegion('Multi Tree Select Field')).toHaveTextContent(
  113. 'key1, key2, key2::key2_child1',
  114. )
  115. expect(getRegion('External Attribute')).toHaveTextContent(
  116. 'Display External',
  117. )
  118. expect(
  119. getByRole(getRegion('Phone'), 'link', { name: '+49 123456789' }),
  120. ).toHaveAttribute('href', 'tel:+49123456789')
  121. expect(
  122. getByRole(getRegion('Email'), 'link', { name: 'email@email.com' }),
  123. ).toHaveAttribute('href', 'mailto:email@email.com')
  124. expect(
  125. getByRole(getRegion('Url'), 'link', { name: 'https://url.com' }),
  126. ).toHaveAttribute('href', 'https://url.com')
  127. expect(
  128. view.queryByRole('region', { name: 'Hidden Boolean' }),
  129. ).not.toBeInTheDocument()
  130. })
  131. test("don't show empty fields", () => {
  132. const object = {
  133. login: '',
  134. objectAttributesValues: [
  135. {
  136. attribute: attributesByKey.integer_field,
  137. value: 0,
  138. },
  139. {
  140. attribute: attributesByKey.multi_select_field,
  141. value: [],
  142. },
  143. ],
  144. }
  145. const view = renderComponent(ObjectAttributes, {
  146. props: {
  147. object,
  148. attributes: [attributesByKey.login],
  149. },
  150. })
  151. expect(view.queryAllByRole('region')).toHaveLength(0)
  152. })
  153. it('translates translatable', () => {
  154. mockPermissions(['admin.user', 'ticket.agent'])
  155. const object = {
  156. vip: true,
  157. single_select: 'key1',
  158. multi_select_field: ['key1', 'key2'],
  159. single_tree_select: 'key1::key1_child1',
  160. multi_tree_select: ['key1', 'key1::key1_child1'],
  161. }
  162. const translatable = (attr: any) => ({
  163. ...attr,
  164. dataOption: {
  165. ...attr.dataOption,
  166. translate: true,
  167. },
  168. })
  169. const attributes = [
  170. translatable(attributesByKey.vip),
  171. translatable(attributesByKey.single_select),
  172. translatable(attributesByKey.multi_select_field),
  173. translatable(attributesByKey.single_tree_select),
  174. translatable(attributesByKey.multi_tree_select),
  175. ]
  176. i18n.setTranslationMap(
  177. new Map([
  178. ['yes', 'sí'],
  179. ['Display1', 'Monitor1'],
  180. ['Display2', 'Monitor2'],
  181. ['key1', 'llave1'],
  182. ['key2', 'llave2'],
  183. ['key1_child1', 'llave1_niño1'],
  184. ]),
  185. )
  186. const view = renderComponent(ObjectAttributes, {
  187. props: {
  188. object,
  189. attributes,
  190. },
  191. router: true,
  192. })
  193. const getRegion = (name: string) => view.getByRole('region', { name })
  194. const vip = getRegion('VIP')
  195. const singleSelect = getRegion('Single Select Field')
  196. const multiSelect = getRegion('Multi Select Field')
  197. const singleTreeSelect = getRegion('Single Tree Select Field')
  198. const multiTreeSelect = getRegion('Multi Tree Select Field')
  199. expect(vip).toHaveTextContent('sí')
  200. expect(singleSelect).toHaveTextContent('Monitor1')
  201. expect(multiSelect).toHaveTextContent('Monitor1, Monitor2')
  202. expect(singleTreeSelect).toHaveTextContent('llave1::llave1_niño1')
  203. expect(multiTreeSelect).toHaveTextContent('llave1, llave1::llave1_niño1')
  204. })
  205. it('renders different dates', async () => {
  206. const object = {
  207. now: '2021-04-09T10:11:12Z',
  208. past: '2021-02-09T10:11:12Z',
  209. future: '2021-10-09T10:11:12Z',
  210. }
  211. const attributes = [
  212. { ...attributesByKey.date_time_field, name: 'now', display: 'now' },
  213. { ...attributesByKey.date_time_field, name: 'past', display: 'past' },
  214. { ...attributesByKey.date_time_field, name: 'future', display: 'future' },
  215. ]
  216. const view = renderComponent(ObjectAttributes, {
  217. props: {
  218. object,
  219. attributes,
  220. },
  221. router: true,
  222. })
  223. const getRegion = (time: string) => view.getByRole('region', { name: time })
  224. expect(getRegion('now')).toHaveTextContent('2021-04-09 10:11')
  225. expect(getRegion('past')).toHaveTextContent('2021-02-09 10:11')
  226. expect(getRegion('future')).toHaveTextContent('2021-10-09 10:11')
  227. mockApplicationConfig({
  228. pretty_date_format: 'relative',
  229. })
  230. await flushPromises()
  231. expect(getRegion('now')).toHaveTextContent('just now')
  232. expect(getRegion('past')).toHaveTextContent('1 month ago')
  233. expect(getRegion('future')).toHaveTextContent('in 6 months')
  234. })
  235. it('doesnt render skipped attributes', () => {
  236. const object = {
  237. skip: 'skip',
  238. show: 'show',
  239. }
  240. const attributes = [
  241. { ...attributesByKey.address, name: 'skip', display: 'skip' },
  242. { ...attributesByKey.address, name: 'show', display: 'show' },
  243. ]
  244. const view = renderComponent(ObjectAttributes, {
  245. props: {
  246. object,
  247. attributes,
  248. skipAttributes: ['skip'],
  249. },
  250. router: true,
  251. })
  252. expect(view.getByRole('region', { name: 'show' })).toBeInTheDocument()
  253. expect(view.queryByRole('region', { name: 'skip' })).not.toBeInTheDocument()
  254. })
  255. it('renders links', () => {
  256. const object = {
  257. objectAttributeValues: [
  258. {
  259. attribute: {
  260. ...attributesByKey.integer_field,
  261. dataOption: {
  262. ...attributesByKey.integer_field.dataOption,
  263. linktemplate: 'https://integer.com/#{render}',
  264. },
  265. },
  266. value: 600,
  267. renderedLink: 'https://integer.com/rendered',
  268. },
  269. {
  270. attribute: attributesByKey.some_url,
  271. value: 'https://url.com',
  272. },
  273. {
  274. attribute: {
  275. ...attributesByKey.some_email,
  276. dataOption: {
  277. ...attributesByKey.integer_field.dataOption,
  278. linktemplate: 'https://email.com/#{render}',
  279. },
  280. },
  281. value: 'email@email.com',
  282. renderedLink: 'https://email.com/rendered',
  283. },
  284. {
  285. attribute: {
  286. ...attributesByKey.phone,
  287. dataOption: {
  288. ...attributesByKey.integer_field.dataOption,
  289. linktemplate: 'https://phone.com/#{render}',
  290. },
  291. },
  292. value: '+49 123456789',
  293. renderedLink: 'https://phone.com/rendered',
  294. },
  295. ],
  296. }
  297. const attributes = object.objectAttributeValues.map((v) => v.attribute)
  298. const view = renderComponent(ObjectAttributes, {
  299. props: {
  300. object,
  301. attributes,
  302. skipAttributes: ['skip'],
  303. },
  304. router: true,
  305. })
  306. const getRegion = (name: string) => view.getByRole('region', { name })
  307. expect(
  308. getByRole(getRegion('Integer Field'), 'link', { name: '600' }),
  309. ).toHaveAttribute('href', 'https://integer.com/rendered')
  310. expect(
  311. getByRole(getRegion('Phone'), 'link', { name: '+49 123456789' }),
  312. ).toHaveAttribute('href', 'https://phone.com/rendered')
  313. expect(
  314. getByRole(getRegion('Email'), 'link', { name: 'email@email.com' }),
  315. ).toHaveAttribute('href', 'https://email.com/rendered')
  316. expect(
  317. getByRole(getRegion('Url'), 'link', { name: 'https://url.com' }),
  318. ).toHaveAttribute('href', 'https://url.com')
  319. })
  320. })