-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactor: change table provider scope * chore: change table playground components * feat: add caption component * test: update table test suite
- Loading branch information
1 parent
3a547eb
commit d58550b
Showing
24 changed files
with
288 additions
and
156 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
packages/aware-components/src/components/Table/Caption.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import React, { useEffect } from 'react'; | ||
import { DEVELOPMENT } from '../../constants'; | ||
import { | ||
addCaption, | ||
deleteCaption, | ||
useTable, | ||
} from '../../context/table/actions'; | ||
|
||
type Props = React.DetailedHTMLProps< | ||
React.HTMLAttributes<HTMLTableCaptionElement>, | ||
HTMLTableCaptionElement | ||
>; | ||
|
||
export function Development(props: Props) { | ||
const { children, ...rest } = props; | ||
const { dispatch } = useTable(); | ||
|
||
useEffect(() => { | ||
dispatch(addCaption()); | ||
return () => dispatch(deleteCaption()); | ||
}, [dispatch]); | ||
|
||
return <caption {...rest}>{children}</caption>; | ||
} | ||
|
||
export const Caption = (props: Props) => | ||
DEVELOPMENT ? ( | ||
<Development {...props} /> | ||
) : ( | ||
<caption {...props}>{props.children}</caption> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ export { | |
A, | ||
Audio, | ||
Button, | ||
Caption, | ||
Div, | ||
Fieldset, | ||
H1, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,78 +1,142 @@ | ||
import React from 'react'; | ||
import { describe, expect, it } from 'vitest'; | ||
import { tableChecks } from '../../utils/a11y/table'; | ||
import { checkColHeader } from '../../utils/a11y/table/checks/checkColHeader'; | ||
import { checkMultiHeader } from '../../utils/a11y/table/checks/checkMultiHeader'; | ||
import { checkRowHeader } from '../../utils/a11y/table/checks/checkRowHeader'; | ||
import { Scope } from '../../context/table/types'; | ||
import { tableChecks } from '../../utils/a11y/table/index'; | ||
import { messages } from '../../utils/messages'; | ||
|
||
describe('Table element accessibility checks', () => { | ||
const createElementWithScopeAndId = ( | ||
type: 'th' | 'td', | ||
scope?: string, | ||
id?: string | ||
) => React.createElement(type, { scope, id, key: id ?? '1' }); | ||
|
||
const validHeader = createElementWithScopeAndId('th', 'col', 'header1'); | ||
const invalidHeaderNoScope = createElementWithScopeAndId('th'); | ||
const rowHeaders = [validHeader]; | ||
const colHeaders = [validHeader]; | ||
const headerColumns = [validHeader, validHeader]; | ||
|
||
describe('checkRowHeader', () => { | ||
it('should return null if all row headers have scope', () => { | ||
expect(checkRowHeader(colHeaders, rowHeaders)).toBe(null); | ||
}); | ||
|
||
it('should return a message if any row header is missing scope', () => { | ||
const result = checkRowHeader(colHeaders, [invalidHeaderNoScope]); | ||
expect(result).toBe(messages.table.row); | ||
}); | ||
describe('Accessibility check for table', () => { | ||
const colHeading: Scope[] = ['col']; | ||
const twoHeadings: Scope[] = ['col', 'row']; | ||
const multiHeadings: Scope[] = ['col', 'col', 'row', 'row']; | ||
|
||
const caption = React.createElement('caption', {}, 'Caption'); | ||
|
||
const createThead = ( | ||
children: React.DetailedReactHTMLElement<object, HTMLElement> | ||
) => React.createElement('thead', {}, children); | ||
|
||
const createTr = ( | ||
children: React.DetailedReactHTMLElement<object, HTMLElement>[] | ||
) => React.createElement('tr', {}, ...children); | ||
|
||
const createTh = () => React.createElement('th', {}); | ||
|
||
const createTd = () => React.createElement('td', {}); | ||
|
||
const createThWithScope = () => React.createElement('th', { scope: 'scope' }); | ||
|
||
const createThWithAttributes = () => | ||
React.createElement('th', { scope: 'scope', id: 'id' }); | ||
|
||
const tableHeader = { | ||
children: [createThead(createTr([createTh(), createTh(), createTh()]))], | ||
}; | ||
|
||
const thWithScopeAndId = { | ||
children: [ | ||
createThead( | ||
createTr( | ||
Array(3) | ||
.fill(undefined) | ||
.map(() => createThWithAttributes()) | ||
) | ||
), | ||
], | ||
}; | ||
|
||
const thWithScope = { | ||
children: [ | ||
createThead( | ||
createTr( | ||
Array(3) | ||
.fill(undefined) | ||
.map(() => createThWithScope()) as React.DetailedReactHTMLElement< | ||
object, | ||
HTMLElement | ||
>[] | ||
) | ||
), | ||
], | ||
}; | ||
|
||
const tableBody = { | ||
children: createTr( | ||
Array(5) | ||
.fill(undefined) | ||
.map((_, i) => (i === 0 ? createTh() : createTd())) | ||
), | ||
}; | ||
|
||
it('warns if no column heading is provided', () => { | ||
const warnings = tableChecks({}, [], undefined); | ||
expect(warnings).toContain(messages.table.col); | ||
}); | ||
|
||
describe('checkColHeader', () => { | ||
it('should return a message if no column headers are provided', () => { | ||
expect(checkColHeader([])).toBe(messages.table.col); | ||
}); | ||
it('passes if column heading is defined by scope', () => { | ||
const warnings = tableChecks({}, colHeading, undefined); | ||
expect(warnings.length).toEqual(0); | ||
}); | ||
|
||
it('passes if column heading is defined via props', () => { | ||
const warnings = tableChecks(tableHeader, [], undefined); | ||
expect(warnings.length).toEqual(0); | ||
}); | ||
|
||
it('warns if two-heading table lacks `scope`', () => { | ||
const warnings = tableChecks( | ||
{ children: [...tableHeader.children, tableBody.children] }, | ||
[], | ||
undefined | ||
); | ||
expect(warnings).toContain(messages.table.row); | ||
}); | ||
|
||
it('warns if two-heading table has no `scope`', () => { | ||
const warnings = tableChecks(tableBody, twoHeadings, undefined); | ||
expect(warnings).toContain(messages.table.row); | ||
}); | ||
|
||
it('passes if all <th> elements in two-heading table have `scope`', () => { | ||
const warnings = tableChecks(thWithScope, twoHeadings, undefined); | ||
expect(warnings.length).toEqual(0); | ||
}); | ||
|
||
it('warns if <th> in multi-heading table lacks `scope`', () => { | ||
const warnings = tableChecks(tableHeader, multiHeadings, undefined); | ||
expect(warnings).toContain(messages.table.multi); | ||
}); | ||
|
||
it('passes if all <th> elements in multi-heading table have `scope`', () => { | ||
const warnings = tableChecks( | ||
{ children: [caption, ...thWithScopeAndId.children] }, | ||
multiHeadings, | ||
undefined | ||
); | ||
expect(warnings.length).toEqual(0); | ||
}); | ||
|
||
it('warns if multi-heading table lacks `caption`', () => { | ||
const warnings = tableChecks(thWithScopeAndId, multiHeadings, undefined); | ||
expect(warnings).toContain(messages.table.caption); | ||
}); | ||
|
||
it('passes if `caption` is provided via context in multi-heading table', () => { | ||
const warnings = tableChecks(thWithScopeAndId, multiHeadings, true); | ||
expect(warnings.length).toEqual(0); | ||
}); | ||
|
||
it('should return null if column headers are provided', () => { | ||
expect(checkColHeader(headerColumns)).toBe(null); | ||
}); | ||
it('issues one warning if no headings exist in table', () => { | ||
const warnings = tableChecks({}, [], undefined); | ||
expect(warnings.length).toEqual(1); | ||
}); | ||
|
||
describe('checkMultiHeader', () => { | ||
it('should return null if all headers have scope and id for multi-level headers', () => { | ||
expect( | ||
checkMultiHeader(colHeaders, rowHeaders, headerColumns.length, []) | ||
).toBe(null); | ||
}); | ||
|
||
it('should return a message if any header in multi-level headers is missing scope or id', () => { | ||
const result = checkMultiHeader( | ||
[invalidHeaderNoScope], | ||
rowHeaders, | ||
headerColumns.length, | ||
[] | ||
); | ||
expect(result).toBe(messages.table.multi); | ||
}); | ||
it('issues one warning if a two-heading table fails checks', () => { | ||
const warnings = tableChecks(tableHeader, twoHeadings, undefined); | ||
expect(warnings.length).toEqual(1); | ||
}); | ||
|
||
describe('tableChecks', () => { | ||
it('should return an empty array if all checks pass', () => { | ||
const mockTableProps = { | ||
children: [ | ||
React.createElement('caption', {}, 'Caption'), | ||
React.createElement('tr', { key: 'table1' }, [ | ||
createElementWithScopeAndId('th', 'col', 'header1'), | ||
createElementWithScopeAndId('th', 'col', 'header1'), | ||
createElementWithScopeAndId('td', undefined, 'data1'), | ||
]), | ||
], | ||
}; | ||
|
||
const result = tableChecks(mockTableProps); | ||
expect(result).toEqual([]); | ||
}); | ||
it('issues two warnings if multi-heading table fails checks', () => { | ||
const warnings = tableChecks(tableHeader, multiHeadings, undefined); | ||
expect(warnings.length).toEqual(2); | ||
}); | ||
}); |
10 changes: 2 additions & 8 deletions
10
packages/aware-components/src/utils/a11y/table/checks/checkCaption.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,11 @@ | ||
import { ReactElement } from 'react'; | ||
import { ARIA_DESCRIBEDBY, CAPTION } from '../../../../constants'; | ||
import { getFirstChild } from '../../../../helper/children'; | ||
import { messages } from '../../../messages'; | ||
import { TableProps } from '../types'; | ||
|
||
export const checkCaption = ( | ||
props: TableProps, | ||
rowHeaders: ReactElement[], | ||
headerCount: number | ||
) => | ||
rowHeaders.length && | ||
headerCount > 1 && | ||
export const checkCaption = (props: TableProps, caption: boolean | undefined) => | ||
getFirstChild(props.children)?.type !== CAPTION && | ||
!caption && | ||
!props[ARIA_DESCRIBEDBY] | ||
? messages.table.caption | ||
: null; |
5 changes: 2 additions & 3 deletions
5
packages/aware-components/src/utils/a11y/table/checks/checkColHeader.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
import { ReactElement } from 'react'; | ||
import { messages } from '../../../messages'; | ||
|
||
export const checkColHeader = (colHeaders: ReactElement[]) => | ||
!colHeaders.length ? messages.table.col : null; | ||
export const checkColHeader = (hasColumnHeader: boolean) => | ||
!hasColumnHeader ? messages.table.col : null; |
Oops, something went wrong.