@@ -13,6 +13,7 @@ import type {
1313 AriaNode ,
1414 AriaRegex ,
1515 AriaTemplateNode ,
16+ AriaTemplateRoleNode ,
1617} from './folk/isomorphic/ariaSnapshot'
1718import { formatTextValue , formatNameValue } from './template'
1819
@@ -91,13 +92,9 @@ import { formatTextValue, formatNameValue } from './template'
9192 *
9293 * Invariant:
9394 * pass = true <=> resolved = expected
94- * TODO: right? but this doesn't hold yet.
95- * we only have one direction
96- * pass = true <= resolved = expected
97- * or equivalently
98- * pass = false => resolved != expected
99- * This shouldn't affect user facing behavior since
100- * when pass = true, it won't show error diff nor create new snapshot.
95+ * TODO:
96+ * This holds all cases except `aria-expanded` tri-state behaviors,
97+ * which we inherited from playwright. We leave this case for now.
10198 *
10299 * Diff display (pass: false):
103100 * Use resolved vs expected. The user sees their original assertion
@@ -159,6 +156,49 @@ function renderKeyWithName(
159156 return key
160157}
161158
159+ /** Build the resolved key through the template's lens:
160+ * only include name/attributes that the template mentions. */
161+ function renderResolvedKey ( node : AriaNode , template : AriaTemplateRoleNode ) : string {
162+ let key = node . role as string
163+
164+ // Name: omit if template omits, adopt regex if matched, literal otherwise
165+ if ( template . name === undefined ) {
166+ // template doesn't care about name → omit
167+ } else if (
168+ isRegexName ( template . name ) &&
169+ matchesStringOrRegex ( node . name , template . name )
170+ ) {
171+ key += ` ${ formatNameValue ( template . name ) } `
172+ } else {
173+ if ( node . name ) {
174+ key += ` ${ JSON . stringify ( node . name ) } `
175+ }
176+ }
177+
178+ // Attributes: only render what the template mentions
179+ if ( template . level !== undefined ) key += ` [level=${ node . level } ]`
180+ if ( template . checked !== undefined ) {
181+ if ( node . checked === true ) key += ' [checked]'
182+ else if ( node . checked === 'mixed' ) key += ' [checked=mixed]'
183+ }
184+ if ( template . disabled !== undefined && node . disabled ) {
185+ key += ' [disabled]'
186+ }
187+ if ( template . expanded !== undefined ) {
188+ if ( node . expanded === true ) key += ' [expanded]'
189+ else if ( node . expanded === false ) key += ' [expanded=false]'
190+ }
191+ if ( template . pressed !== undefined ) {
192+ if ( node . pressed === true ) key += ' [pressed]'
193+ else if ( node . pressed === 'mixed' ) key += ' [pressed=mixed]'
194+ }
195+ if ( template . selected !== undefined && node . selected ) {
196+ key += ' [selected]'
197+ }
198+
199+ return key
200+ }
201+
162202// --- Pairing + merge ---
163203
164204function pairChildren (
@@ -290,18 +330,13 @@ function mergeNode(
290330 let namePass = matchesStringOrRegex ( node . name , template . name )
291331
292332 // Resolved key (e.g. `- heading "Hello" [level=1]`):
293- // adopt the template's lens for the name.
294- // template omits name (e.g. `- heading`) → resolved omits it
333+ // adopt the template's lens for both name and attributes .
334+ // template omits name (e.g. `- heading`) → resolved omits it
295335 // template has regex (e.g. `- button /\d+/`) → resolved adopts regex if matched
296336 // template has literal (e.g. `- button "Save"`) → resolved uses literal
297- let resolvedKey : string
298- if ( template . name === undefined ) {
299- resolvedKey = `${ node . role } ${ renderAriaProps ( node ) } `
300- } else if ( namePass && isRegexName ( template . name ) ) {
301- resolvedKey = renderKeyWithName ( node , template . name )
302- } else {
303- resolvedKey = createAriaKey ( node )
304- }
337+ // template omits attr (e.g. no [level]) → resolved omits it
338+ // template has attr (e.g. [level=1]) → resolved includes it
339+ const resolvedKey = renderResolvedKey ( node , template )
305340
306341 // Recurse into children — if template omits children, the lens says
307342 // "don't care", so we skip (don't render children in resolved output).
0 commit comments