|
5 | 5 | * `fields` projection (objectstack#26991): each row carries the field's |
6 | 6 | * authored label, a picklist value its authored option label, a currency |
7 | 7 | * value its currency through `resolveFieldCurrency`, and a filled lookup a |
8 | | - * neutral "Linked record" in place of its stored id (objectstack#31326). A key |
| 8 | + * neutral "Linked record" in place of its stored id (objectstack#31326), a |
| 9 | + * filled user field "Linked user" in place of its sys_user id |
| 10 | + * (objectstack#31350). A key |
9 | 11 | * with no `fields` entry, and an answer from a server that predates the |
10 | 12 | * projection, keep the humanized label and the JSON-shape value. |
11 | 13 | * |
@@ -344,3 +346,84 @@ describe('SharedRecordPage — a lookup shows a neutral placeholder, never its s |
344 | 346 | expect(cellOf('Record ID').textContent).toBe('opp_7'); |
345 | 347 | }); |
346 | 348 | }); |
| 349 | + |
| 350 | +/** |
| 351 | + * objectstack#31350 — a user field stores a `sys_user` id, the same way a |
| 352 | + * lookup stores its target's. The projection serves it as `type: 'user'`, and |
| 353 | + * the page shows a neutral, localized "Linked user" for a filled one and the |
| 354 | + * empty face for an empty one; the id never reaches the DOM, and nothing reads |
| 355 | + * the user record. |
| 356 | + */ |
| 357 | +describe('SharedRecordPage — a user field shows a neutral placeholder, never its sys_user id (objectstack#31350)', () => { |
| 358 | + /** A `ticket` as resolve hands it over: a filled and an empty user field, a multiple one, a lookup beside them. */ |
| 359 | + const TICKET = { |
| 360 | + id: 'tkt_3', |
| 361 | + subject: 'Printer on fire', |
| 362 | + assignee: 'usr_7d41a0', |
| 363 | + reviewer: null, |
| 364 | + watchers: ['usr_w_alpha', 'usr_w_beta'], |
| 365 | + backup: [], |
| 366 | + account_id: 'acc_t_55', |
| 367 | + }; |
| 368 | + const TICKET_FIELDS = { |
| 369 | + id: { label: 'Record ID', type: 'text' }, |
| 370 | + subject: { label: 'Subject', type: 'text' }, |
| 371 | + assignee: { label: 'Assigned To', type: 'user', reference: 'sys_user' }, |
| 372 | + reviewer: { label: 'Reviewer', type: 'user', reference: 'sys_user' }, |
| 373 | + watchers: { label: 'Watchers', type: 'user', reference: 'sys_user', multiple: true }, |
| 374 | + backup: { label: 'Backup', type: 'user', reference: 'sys_user', multiple: true }, |
| 375 | + account_id: { label: 'Account', type: 'lookup', reference: 'account', displayField: 'name' }, |
| 376 | + }; |
| 377 | + const USER_IDS = ['usr_7d41a0', 'usr_w_alpha', 'usr_w_beta']; |
| 378 | + |
| 379 | + it('a filled user field shows the placeholder, its id appears nowhere in the DOM, and only resolve is fetched', async () => { |
| 380 | + const requested = shareServer(TICKET, TICKET_FIELDS, 'ticket'); |
| 381 | + renderLink(); |
| 382 | + |
| 383 | + await screen.findByText('Shared ticket'); |
| 384 | + expect(cellOf('Assigned To').textContent).toBe('Linked user'); |
| 385 | + expect(document.body.innerHTML).not.toContain('usr_7d41a0'); |
| 386 | + // No read of sys_user, the data source or /meta: one request, to resolve. |
| 387 | + expect(requested).toEqual([RESOLVE_PATH]); |
| 388 | + }); |
| 389 | + |
| 390 | + it('a multiple user field shows the placeholder, and none of its ids', async () => { |
| 391 | + shareServer(TICKET, TICKET_FIELDS, 'ticket'); |
| 392 | + renderLink(); |
| 393 | + |
| 394 | + await screen.findByText('Shared ticket'); |
| 395 | + expect(cellOf('Watchers').textContent).toBe('Linked user'); |
| 396 | + for (const id of USER_IDS) expect(document.body.innerHTML).not.toContain(id); |
| 397 | + }); |
| 398 | + |
| 399 | + it('an empty user field renders empty, as before', async () => { |
| 400 | + shareServer(TICKET, TICKET_FIELDS, 'ticket'); |
| 401 | + renderLink(); |
| 402 | + |
| 403 | + await screen.findByText('Shared ticket'); |
| 404 | + expect(cellOf('Reviewer').textContent).toBe('—'); |
| 405 | + expect(cellOf('Backup').textContent).toBe('—'); |
| 406 | + }); |
| 407 | + |
| 408 | + it('the placeholder speaks the session language', async () => { |
| 409 | + const zh = builtInLocales['zh-Hans'].sharedRecord.linkedUser; |
| 410 | + // Guard: a real zh row, not an English copy. |
| 411 | + expect(zh).not.toBe(builtInLocales.en.sharedRecord.linkedUser); |
| 412 | + shareServer(TICKET, TICKET_FIELDS, 'ticket'); |
| 413 | + renderLink(undefined, 'zh'); |
| 414 | + |
| 415 | + await screen.findByText(builtInLocales['zh-Hans'].sharedRecord.sharedObject.replace('{{object}}', 'ticket')); |
| 416 | + expect(cellOf('Assigned To').textContent).toBe(zh); |
| 417 | + expect(document.body.innerHTML).not.toContain('usr_7d41a0'); |
| 418 | + }); |
| 419 | + |
| 420 | + it('a text field renders its value and a lookup keeps "Linked record" (control)', async () => { |
| 421 | + shareServer(TICKET, TICKET_FIELDS, 'ticket'); |
| 422 | + renderLink(); |
| 423 | + |
| 424 | + await screen.findByText('Shared ticket'); |
| 425 | + expect(cellOf('Subject').textContent).toBe('Printer on fire'); |
| 426 | + expect(cellOf('Record ID').textContent).toBe('tkt_3'); |
| 427 | + expect(cellOf('Account').textContent).toBe('Linked record'); |
| 428 | + }); |
| 429 | +}); |
0 commit comments