@@ -13,10 +13,11 @@ import { test, expect } from '@playwright/test';
1313 * - stageField:false → NO `record:path` stepper despite a `status` field
1414 * named to bait the heuristic
1515 * - related-heavy → related lists surface by prominence (ADR-0085), in the
16- * tabbed tier as record page v3 draws it (objectstack#27055,
17- * Target 8): a `relatedList: 'primary'` list is a body
18- * table in the Details tab, the rest sit behind the
19- * Related tab and self-fetch when it is shown
16+ * one-column stack tier (objectstack#27524): a
17+ * `relatedList: 'primary'` list is a body table in the
18+ * details (objectstack#27055, Target 8), the rest are
19+ * panels of the right column below them and read their
20+ * rows when they come into view
2021 *
2122 * Runs against the console the backend serves at /_console (baseURL from
2223 * playwright.config.ts; storageState carries the admin session). The
@@ -157,56 +158,60 @@ test('stageField:false: status renders as a plain field, no stepper', async ({ p
157158 expect ( errors , 'uncaught page errors on stageField:false detail' ) . toEqual ( [ ] ) ;
158159} ) ;
159160
160- test ( 'ungrouped + related-heavy: flat details and related-list tabs on Contoso' , async ( { page } ) => {
161+ test ( 'ungrouped + related-heavy: flat details and the related panels on Contoso' , async ( { page } ) => {
161162 // The synthesized record page lays itself out by the width of the view it
162- // occupies (objectstack#26678): the tab strip this case opens exists only
163- // below the two-column floor, a record view narrower than
164- // `RECORD_PAGE_TWO_COLUMN_MIN_WIDTH` (760 px, `@object-ui/plugin-detail`).
165- // The case stays in that tabbed tier on purpose: the column tiers are what
166- // every other page of this smoke draws at the Desktop Chrome viewport, and
167- // the tab strip is also what the drawer and split pane draw at any width.
163+ // occupies, by one rule (objectstack#26678, objectstack#27524): details |
164+ // the right column from `RECORD_PAGE_TWO_COLUMN_MIN_WIDTH` (760 px,
165+ // `@object-ui/plugin-detail`), and ONE column below it — the details, then
166+ // the right column's panels in their order. No tier draws a tab strip. The
167+ // case stays in that stack tier on purpose: the column tiers are what every
168+ // other page of this smoke draws at the Desktop Chrome viewport, and the
169+ // stack is also what the split pane (720 px) and a phone draw.
168170 //
169171 // The window is derived, not picked. Measured on this harness (a fresh
170172 // browser context, so no stored sidebar cookie): from a 768 px window
171173 // (`MOBILE_BREAKPOINT`, the desktop shell) to 1000 px the console sidebar
172174 // came up as its 48 px icon rail, and the record view was the window minus
173- // that rail — two columns at 808 px (760 of view), tabs at 800 px (752).
174- // So the desktop tabbed tier is a window of 768 to 807 px, and an expanded
175+ // that rail — two columns at 808 px (760 of view), one at 800 px (752).
176+ // So the desktop stack tier is a window of 768 to 807 px, and an expanded
175177 // sidebar (256 px) only narrows the view further. 780 sits inside it with
176178 // headroom on both sides: 732 px of view against the 760 floor, 12 px above
177179 // the mobile breakpoint. ⛔ Not 1200 px: since objectstack#27473 moved the
178- // floor to a 760 px view, 1200 draws columns and no tablist . ⛔ Not below
179- // 768 px: that is the mobile shell, where the off-canvas sidebar gives the
180- // view the whole window (760 px measured as two columns).
180+ // floor to a 760 px view, 1200 draws two columns . ⛔ Not below 768 px: that
181+ // is the mobile shell, where the off-canvas sidebar gives the view the
182+ // whole window (760 px measured as two columns).
181183 await page . setViewportSize ( { width : 780 , height : 800 } ) ;
182184 const errors = await openRecord ( page , recordUrl ( 'showcase_account' , contosoId ) ) ;
183185
184186 // Heuristic stepper (account.status is a select named "status").
185187 // record:path renders a desktop + a mobile variant — assert the visible one.
186188 await expect ( page . locator ( PATH_STEPPER ) . first ( ) ) . toBeVisible ( ) ;
187189
190+ // One column and no tab strip: the details, then the right column.
191+ await expect ( page . locator ( '[data-record-page-tier]' ) ) . toHaveAttribute ( 'data-record-page-tier' , 'stack' ) ;
192+ await expect ( page . locator ( '[data-record-columns]' ) ) . toHaveAttribute ( 'data-record-columns-shown' , 'details' ) ;
193+ await expect ( page . getByRole ( 'tab' , { name : / D e t a i l s | R e l a t e d | I n v o i c e s | P r o j e c t s / i } ) ) . toHaveCount ( 0 ) ;
194+
188195 // Related lists surface by prominence (ADR-0085), as record page v3 draws
189196 // them (objectstack#27055, Target 8): Invoices and Projects are
190197 // `relatedList: 'primary'` (CORE relationships), so each is a body table in
191- // the Details tab, with its rows read at open; the rest of Contoso's lists
192- // (Contacts and the Field Zoo lookups) sit behind the Related tab.
193- const detailsTab = page . getByRole ( 'tab' , { name : / D e t a i l s / i } ) ;
194- const relatedTab = page . getByRole ( 'tab' , { name : / ^ R e l a t e d / } ) ;
195- await expect ( detailsTab ) . toBeVisible ( ) ;
196- await expect ( detailsTab ) . toHaveAttribute ( 'aria-selected' , 'true' ) ;
197- await expect ( relatedTab ) . toBeVisible ( ) ;
198- await expect ( page . getByRole ( 'tab' , { name : / I n v o i c e s | P r o j e c t s / i } ) ) . toHaveCount ( 0 ) ;
199- const detailsPanel = page . getByRole ( 'tabpanel' , { name : / D e t a i l s / i } ) ;
200- const invoicesTable = detailsPanel
198+ // the details, with its rows read at open; the rest of Contoso's lists
199+ // (Contacts and the Field Zoo lookups) are panels of the right column,
200+ // after its first panel, 动态 (objectstack#27524, Target 5).
201+ const details = page . locator ( '[data-record-column="details"]' ) ;
202+ const invoicesTable = details
201203 . getByRole ( 'table' )
202204 . filter ( { has : page . getByRole ( 'columnheader' , { name : 'Invoice Number' } ) } ) ;
203- const projectsTable = detailsPanel
205+ const projectsTable = details
204206 . getByRole ( 'table' )
205207 . filter ( { has : page . getByRole ( 'columnheader' , { name : 'Project Name' } ) } ) ;
206208 await expect ( invoicesTable . getByRole ( 'cell' , { name : 'INV-1002' , exact : true } ) ) . toBeVisible ( ) ;
207209 await expect ( projectsTable . getByRole ( 'cell' , { name : 'Data Platform' , exact : true } ) ) . toBeVisible ( ) ;
210+ const rightColumn = page . locator ( '[data-record-column="right"]' ) ;
211+ await expect ( rightColumn . locator ( ':scope > *' ) . first ( ) ) . toHaveAttribute ( 'data-record-timeline-panel' , '' ) ;
212+ await expect ( rightColumn . getByRole ( 'region' , { name : / ^ ( I n v o i c e s | P r o j e c t s ) $ / } ) ) . toHaveCount ( 0 ) ;
208213
209- // Ungrouped shape: the Details body is flat — no fieldGroup section cards.
214+ // Ungrouped shape: the details body is flat — no fieldGroup section cards.
210215 // (Contoso declares no fieldGroups; spot-check a body field label.)
211216 await expect ( page . getByText ( 'Basics' , { exact : true } ) ) . toHaveCount ( 0 ) ;
212217
@@ -220,13 +225,11 @@ test('ungrouped + related-heavy: flat details and related-list tabs on Contoso',
220225 expect ( footerText , 'footer dangles "Created by" without an actor' ) . not . toMatch ( / C r e a t e d b y / ) ;
221226 expect ( footerText ) . toMatch ( / C r e a t e d / ) ;
222227
223- // The non-primary lists self-fetch lazily when their tab is shown: the
224- // Related tab's Contacts list reads Contoso's seeded contacts.
225- await relatedTab . click ( ) ;
226- await expect ( relatedTab ) . toHaveAttribute ( 'aria-selected' , 'true' ) ;
227- const relatedPanel = page . getByRole ( 'tabpanel' , { name : / ^ R e l a t e d / } ) ;
228- await expect ( relatedPanel ) . toBeVisible ( ) ;
229- await expect ( relatedPanel . getByRole ( 'cell' , { name : 'Carl Fox' , exact : true } ) ) . toBeVisible ( ) ;
228+ // A panel reads its rows once it is in view: below the details here, so
229+ // the Contacts panel reads Contoso's seeded contacts when scrolled to.
230+ const contactsPanel = rightColumn . getByRole ( 'region' , { name : 'Contacts' , exact : true } ) ;
231+ await contactsPanel . scrollIntoViewIfNeeded ( ) ;
232+ await expect ( contactsPanel . locator ( '[data-related-card-row]' ) . filter ( { hasText : 'Carl Fox' } ) ) . toBeVisible ( ) ;
230233
231234 expect ( errors , 'uncaught page errors on related-heavy detail' ) . toEqual ( [ ] ) ;
232235} ) ;
0 commit comments