ObjectAttributes.spec.ts 12 KB

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