Skip to content

Commit

Permalink
feat(table): support w3c WAI-ARIA (a11y) pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
mlmoravek committed Nov 27, 2024
1 parent bc6269a commit 694f57d
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 9 deletions.
5 changes: 3 additions & 2 deletions packages/oruga/src/components/loading/Loading.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ const props = withDefaults(defineProps<LoadingProps>(), {
icon: () => getDefault("loading.icon", "loading"),
iconSpin: () => getDefault("loading.iconSpin", true),
iconSize: () => getDefault("loading.iconSize", "medium"),
scroll: () => getDefault("modal.scroll", "keep"),
scroll: () => getDefault("loading.scroll", "keep"),
ariaRole: () => getDefault("loading.ariaRole", "dialog"),
});
const emits = defineEmits<{
Expand Down Expand Up @@ -127,7 +128,7 @@ defineExpose({ close });
v-if="isActive"
ref="rootElement"
data-oruga="loading"
role="dialog"
:role="ariaRole"
:class="rootClasses">
<div
:class="overlayClasses"
Expand Down
2 changes: 2 additions & 0 deletions packages/oruga/src/components/loading/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export type LoadingProps = {
* @values keep, clip
*/
scroll?: "keep" | "clip";
/** Role attribute to be passed to the div wrapper for better accessibility */
ariaRole?: string;
} & LoadingClasses;

// class props (will not be displayed in the docs)
Expand Down
9 changes: 5 additions & 4 deletions packages/oruga/src/components/table/Table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,8 @@ defineExpose({ rows: tableRows, sort: sortByField });
<th
v-if="showDetailRowIcon"
:class="[...thBaseClasses, ...thDetailedClasses]"
:aria-colindex="1" />
:aria-colindex="1"
aria-hidden="true" />

<!-- checkable column left -->
<th
Expand Down Expand Up @@ -1356,7 +1357,8 @@ defineExpose({ rows: tableRows, sort: sortByField });
<!-- detailed toggle column -->
<th
v-if="showDetailRowIcon"
:class="[...thBaseClasses, ...thDetailedClasses]" />
:class="[...thBaseClasses, ...thDetailedClasses]"
aria-hidden="true" />

<!-- checkable column left -->
<th v-if="checkable && checkboxPosition === 'left'" />
Expand Down Expand Up @@ -1541,7 +1543,6 @@ defineExpose({ rows: tableRows, sort: sortByField });
...column.tdClasses,
]"
:style="isMobileActive ? {} : column.style"
:data-label="column.label"
:props="{
row: row.value,
column,
Expand Down Expand Up @@ -1665,7 +1666,7 @@ defineExpose({ rows: tableRows, sort: sortByField });
:active="loading"
:icon="loadingIcon"
:label="loadingLabel"
role="status" />
aria-role="status" />
</slot>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,27 @@ exports[`OTable tests > render correctly 1`] = `
-->
<!--
@slot Place extra \`o-table-column\` components here, even if you have some columns defined by prop
-->
<!--v-if-->
--><span data-id="1" data-oruga="table-column">ID <!--
Do not render these slots here.
These are only for documentation purposes.
Slots are defined in table component.
--><!--v-if--></span><span data-id="2" data-oruga="table-column">First Name <!--
Do not render these slots here.
These are only for documentation purposes.
Slots are defined in table component.
--><!--v-if--></span><span data-id="3" data-oruga="table-column">Last Name <!--
Do not render these slots here.
These are only for documentation purposes.
Slots are defined in table component.
--><!--v-if--></span><span data-id="4" data-oruga="table-column">Date <!--
Do not render these slots here.
These are only for documentation purposes.
Slots are defined in table component.
--><!--v-if--></span><span data-id="5" data-oruga="table-column">Gender <!--
Do not render these slots here.
These are only for documentation purposes.
Slots are defined in table component.
--><!--v-if--></span>
<!--
@slot Place extra \`o-table-column\` components here, even if you have some columns defined by prop
-->
Expand Down
216 changes: 216 additions & 0 deletions packages/oruga/src/components/table/tests/table.axe.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import { afterEach, describe, expect, test } from "vitest";
import { enableAutoUnmount, mount } from "@vue/test-utils";
import { axe } from "jest-axe";

import OTable from "../Table.vue";
import { nextTick } from "vue";
import type { TableColumn } from "../types";
import type { TableProps } from "../props";

describe("Table axe tests", () => {
enableAutoUnmount(afterEach);

const data = [
{
id: 1,
user: { first_name: "Jesse", last_name: "Simmons" },
date: "2016/10/15 13:43:27",
gender: "Male",
},
{
id: 2,
user: { first_name: "John", last_name: "Jacobs" },
date: "2016/12/15 06:00:53",
gender: "Male",
},
{
id: 3,
user: { first_name: "Tina", last_name: "Gilbert" },
date: "2016/04/26 06:26:28",
gender: "Female",
},
{
id: 4,
user: { first_name: "Clarence", last_name: "Flores" },
date: "2016/04/10 10:28:46",
gender: "Male",
},
{
id: 5,
user: { first_name: "Anne", last_name: "Lee" },
date: "2016/12/06 14:38:38",
gender: "Female",
},
{
id: 6,
user: { first_name: "Sara", last_name: "Armstrong" },
date: "2016/09/23 18:50:04",
gender: "Female",
},
{
id: 7,
user: { first_name: "Anthony", last_name: "Webb" },
date: "2016/08/30 23:49:38",
gender: "Male",
},
{
id: 8,
user: { first_name: "Andrew", last_name: "Greene" },
date: "2016/11/20 14:57:47",
gender: "Male",
},
{
id: 9,
user: { first_name: "Russell", last_name: "White" },
date: "2016/07/13 09:29:49",
gender: "Male",
},
{
id: 10,
user: { first_name: "Lori", last_name: "Hunter" },
date: "2016/12/09 01:44:05",
gender: "Female",
},
{
id: 11,
user: { first_name: "Ronald", last_name: "Wood" },
date: "2016/12/04 02:23:48",
gender: "Male",
},
{
id: 12,
user: { first_name: "Michael", last_name: "Harper" },
date: "2016/07/27 13:28:15",
gender: "Male",
},
{
id: 13,
user: { first_name: "George", last_name: "Dunn" },
date: "2017/03/07 12:26:52",
gender: "Male",
},
{
id: 14,
user: { first_name: "Eric", last_name: "Rogers" },
date: "2016/06/07 05:41:52",
gender: "Male",
},
{
id: 15,
user: { first_name: "Juan", last_name: "Meyer" },
date: "2017/02/01 04:56:34",
gender: "Male",
},
];

const columns: TableColumn<(typeof data)[number]>[] = [
{
field: "id",
label: "ID",
width: "40",
numeric: true,
sortable: true,
},
{
field: "user.first_name",
label: "First Name",
sortable: true,
},
{
field: "user.last_name",
label: "Last Name",
sortable: true,
},
{
field: "date",
label: "Date",
position: "centered",
sortable: true,
formatter: (v): string => new Date(String(v)).toLocaleDateString(),
},
{
field: "gender",
label: "Gender",
sortable: true,
},
];

const a11yCases: {
title: string;
props: TableProps<(typeof data)[number]>;
}[] = [
{
title: "axe table - base case",
props: { data, columns },
},
{
title: "axe table - bordered case",
props: { data, columns, bordered: true },
},
{
title: "axe table - striped case",
props: { data, columns, striped: true },
},
{
title: "axe table - narrowed case",
props: { data, columns, narrowed: true },
},
{
title: "axe table - hoverable case",
props: { data, columns, hoverable: true },
},
{
title: "axe table - selectable case",
props: { data, columns, selectable: true },
},
{
title: "axe table - checkable case",
props: {
data,
columns,
checkable: true,
stickyCheckbox: true,
headerCheckable: true,
},
},
{
title: "axe table - draggable case",
props: { data, columns, draggable: true, draggableColumn: true },
},
{
title: "axe table - scrollable case",
props: {
data,
columns: [...columns, ...columns, ...columns],
scrollable: true,
},
},
{
title: "axe table - detailed case",
props: { data, columns, detailed: true, showDetailIcon: true },
},
{
title: "axe table - pagination case",
props: {
data,
columns,
paginated: true,
perPage: 2,
currentPage: 2,
},
},
{
title: "axe table - loading case",
props: { data, columns, loading: true },
},
];

test.each(a11yCases)("$title", async ({ props }) => {
const wrapper = mount<typeof OTable<(typeof data)[number]>>(OTable, {
props: props,
attachTo: document.body,
});
await nextTick(); // await child component rendering
expect(await axe(wrapper.element)).toHaveNoViolations();
});
});
75 changes: 74 additions & 1 deletion packages/oruga/src/components/table/tests/table.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,85 @@ import { describe, test, expect, afterEach } from "vitest";
import { enableAutoUnmount, mount } from "@vue/test-utils";

import OTable from "@/components/table/Table.vue";
import type { TableColumn } from "../types";

describe("OTable tests", () => {
enableAutoUnmount(afterEach);

const data = [
{
id: 1,
first_name: "Jesse",
last_name: "Simmons",
date: "2016-10-15 13:43:27",
gender: "Male",
},
{
id: 2,
first_name: "John",
last_name: "Jacobs",
date: "2016-12-15 06:00:53",
gender: "Male",
},
{
id: 3,
first_name: "Tina",
last_name: "Gilbert",
date: "2016-04-26 06:26:28",
gender: "Female",
},
{
id: 4,
first_name: "Clarence",
last_name: "Flores",
date: "2016-04-10 10:28:46",
gender: "Male",
},
{
id: 5,
first_name: "Anne",
last_name: "Lee",
date: "2016-12-06 14:38:38",
gender: "Female",
},
];

const columns: TableColumn<(typeof data)[number]>[] = [
{
field: "id",
label: "ID",
width: "40",
numeric: true,
sortable: true,
},
{
field: "first_name",
label: "First Name",
sortable: true,
},
{
field: "last_name",
label: "Last Name",
sortable: true,
},
{
field: "date",
label: "Date",
position: "centered",
sortable: true,
formatter: (v): string => new Date(String(v)).toLocaleDateString(),
},
{
field: "gender",
label: "Gender",
sortable: true,
},
];

test("render correctly", () => {
const wrapper = mount(OTable);
const wrapper = mount<typeof OTable<(typeof data)[number]>>(OTable, {
props: { data, columns },
});
expect(!!wrapper.vm).toBeTruthy();
expect(wrapper.exists()).toBeTruthy();
expect(wrapper.attributes("data-oruga")).toBe("table");
Expand Down

0 comments on commit 694f57d

Please sign in to comment.