|
| 1 | +/** |
| 2 | + * ObjectUI |
| 3 | + * Copyright (c) 2024-present ObjectStack Inc. |
| 4 | + * |
| 5 | + * This source code is licensed under the MIT license found in the |
| 6 | + * LICENSE file in the root directory of this source tree. |
| 7 | + */ |
| 8 | + |
| 9 | +/** |
| 10 | + * objectstack#28484 — on a phone, the ENLARGED related tab hands its rows to |
| 11 | + * `object-grid`, whose card face is the phone list's card row (the name, one |
| 12 | + * subtitle line, one status chip) at the body's full width, rather than to the |
| 13 | + * inset `object-gallery`. The rows are the ones this list read, inline; a tap |
| 14 | + * still reaches the host's `onRowClick` with the row. |
| 15 | + * |
| 16 | + * Controls: a related list that is NOT enlarged keeps the gallery on a phone, |
| 17 | + * and the enlarged tab at a desktop width keeps the table. |
| 18 | + */ |
| 19 | + |
| 20 | +import { describe, it, expect, vi, afterEach } from 'vitest'; |
| 21 | +import { cleanup, render, waitFor } from '@testing-library/react'; |
| 22 | +import * as React from 'react'; |
| 23 | +import { RelatedList } from '../RelatedList'; |
| 24 | +import { RelatedListEnlargedContext } from '../relatedListCompact'; |
| 25 | + |
| 26 | +/** Capture the schema `RelatedList` hands to `SchemaRenderer`. */ |
| 27 | +const h = vi.hoisted(() => ({ schema: null as any })); |
| 28 | +vi.mock('@object-ui/react', async (importOriginal) => { |
| 29 | + const actual = await importOriginal<Record<string, unknown>>(); |
| 30 | + return { |
| 31 | + ...actual, |
| 32 | + SchemaRenderer: (props: any) => { |
| 33 | + h.schema = props.schema; |
| 34 | + return null; |
| 35 | + }, |
| 36 | + }; |
| 37 | +}); |
| 38 | + |
| 39 | +const ORIGINAL_INNER_WIDTH = window.innerWidth; |
| 40 | +function setWidth(px: number) { |
| 41 | + Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: px }); |
| 42 | +} |
| 43 | + |
| 44 | +afterEach(() => { |
| 45 | + cleanup(); |
| 46 | + h.schema = null; |
| 47 | + setWidth(ORIGINAL_INNER_WIDTH); |
| 48 | +}); |
| 49 | + |
| 50 | +const ROWS = [ |
| 51 | + { id: 'c1', name: 'Ada Lovelace', title: 'Director of Sales', status: 'active', lead: 'L-1' }, |
| 52 | + { id: 'c2', name: 'Alan Turing', title: 'CTO', status: 'inactive', lead: 'L-1' }, |
| 53 | +]; |
| 54 | + |
| 55 | +function renderList({ enlarged, onRowClick }: { enlarged: boolean; onRowClick?: (...args: unknown[]) => void }) { |
| 56 | + const dataSource = { |
| 57 | + find: vi.fn(async () => ({ data: ROWS, total: ROWS.length })), |
| 58 | + getObjectSchema: vi.fn(async () => ({ |
| 59 | + name: 'contact', |
| 60 | + fields: { |
| 61 | + name: { type: 'text', label: 'Name' }, |
| 62 | + title: { type: 'text', label: 'Title' }, |
| 63 | + status: { type: 'select', label: 'Status', options: [{ value: 'active', label: 'Active' }] }, |
| 64 | + lead: { type: 'lookup', reference: 'lead', label: 'Lead' }, |
| 65 | + }, |
| 66 | + })), |
| 67 | + }; |
| 68 | + return render( |
| 69 | + <RelatedListEnlargedContext.Provider value={enlarged}> |
| 70 | + <RelatedList |
| 71 | + title="Contacts" |
| 72 | + type="table" |
| 73 | + api="contact" |
| 74 | + objectName="contact" |
| 75 | + referenceField="lead" |
| 76 | + parentId="L-1" |
| 77 | + pageSize={25} |
| 78 | + columns={[{ field: 'name', label: 'Name' }, { field: 'title', label: 'Title' }, { field: 'status', label: 'Status' }]} |
| 79 | + onRowClick={onRowClick as never} |
| 80 | + dataSource={dataSource as never} |
| 81 | + /> |
| 82 | + </RelatedListEnlargedContext.Provider>, |
| 83 | + ); |
| 84 | +} |
| 85 | + |
| 86 | +describe('the enlarged related tab on a phone draws the phone list card rows (objectstack#28484)', () => { |
| 87 | + it('hands object-grid the rows it read, inline, with its columns', async () => { |
| 88 | + setWidth(390); |
| 89 | + renderList({ enlarged: true }); |
| 90 | + await waitFor(() => expect(h.schema?.data?.items).toHaveLength(2)); |
| 91 | + expect(h.schema.type).toBe('object-grid'); |
| 92 | + expect(h.schema.objectName).toBe('contact'); |
| 93 | + expect(h.schema.data.provider).toBe('value'); |
| 94 | + expect(h.schema.data.items.map((row: any) => row.id)).toEqual(['c1', 'c2']); |
| 95 | + expect(h.schema.columns).toEqual(['name', 'title', 'status']); |
| 96 | + expect(h.schema.gallery, 'no gallery config rides along').toBeUndefined(); |
| 97 | + }); |
| 98 | + |
| 99 | + it('a tapped card still reaches the host\'s onRowClick with the row', async () => { |
| 100 | + setWidth(390); |
| 101 | + const onRowClick = vi.fn(); |
| 102 | + renderList({ enlarged: true, onRowClick }); |
| 103 | + await waitFor(() => expect(h.schema?.data?.items).toHaveLength(2)); |
| 104 | + h.schema.onRowClick(ROWS[1]); |
| 105 | + expect(onRowClick).toHaveBeenCalledTimes(1); |
| 106 | + expect(onRowClick.mock.calls[0][0]).toMatchObject({ id: 'c2' }); |
| 107 | + }); |
| 108 | + |
| 109 | + it('CONTROL: a related list that is not enlarged keeps the gallery on a phone', async () => { |
| 110 | + setWidth(390); |
| 111 | + renderList({ enlarged: false }); |
| 112 | + await waitFor(() => expect(h.schema?.type).toBe('object-gallery')); |
| 113 | + }); |
| 114 | + |
| 115 | + it('CONTROL: the enlarged tab at a desktop width keeps the table', async () => { |
| 116 | + setWidth(1280); |
| 117 | + renderList({ enlarged: true }); |
| 118 | + await waitFor(() => expect(h.schema?.type).toBe('data-table')); |
| 119 | + }); |
| 120 | +}); |
0 commit comments