Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions js/atoms/Block.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { test, describe, assert, afterEach, beforeEach } from "vitest";
import { tick, mount, unmount } from "svelte";

import Block from "./src/Block.svelte";

describe("Block Accessibility", () => {
let target: HTMLElement;
let component: any;

beforeEach(() => {
target = document.body.appendChild(document.createElement("div"));
});

afterEach(() => {
if (component) {
unmount(component);
}
if (target.parentNode) {
target.parentNode.removeChild(target);
}
});

test("renders with aria-label when label prop is provided", async () => {
component = mount(Block, {
target,
props: {
label: "Test Block Label"
}
});
await tick();
const block = target.querySelector(".block");
assert.equal(block?.getAttribute("aria-label"), "Test Block Label");
});

test("renders with role='group' when type is fieldset", async () => {
component = mount(Block, {
target,
props: {
type: "fieldset",
label: "Fieldset Group"
}
});
await tick();
const block = target.querySelector(".block");
assert.equal(block?.getAttribute("role"), "group");
assert.equal(block?.tagName.toLowerCase(), "fieldset");
});

test("renders without role when type is normal", async () => {
component = mount(Block, {
target,
props: {
type: "normal"
}
});
await tick();
const block = target.querySelector(".block");
assert.isNull(block?.getAttribute("role"));
assert.equal(block?.tagName.toLowerCase(), "div");
});

test("renders as div by default", async () => {
component = mount(Block, {
target,
props: {}
});
await tick();
const block = target.querySelector(".block");
assert.equal(block?.tagName.toLowerCase(), "div");
});

test("renders as fieldset when type is fieldset", async () => {
component = mount(Block, {
target,
props: {
type: "fieldset"
}
});
await tick();
const block = target.querySelector(".block");
assert.equal(block?.tagName.toLowerCase(), "fieldset");
});
});
3 changes: 3 additions & 0 deletions js/atoms/src/Block.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
export let resizable = false;
export let rtl = false;
export let fullscreen = false;
export let label: string | undefined = undefined;
let old_fullscreen = fullscreen;

let element: HTMLElement;
Expand Down Expand Up @@ -129,6 +130,8 @@
style:border-width="var(--block-border-width)"
class:auto-margin={scale === null}
dir={rtl ? "rtl" : "ltr"}
role={type === "fieldset" ? "group" : undefined}
aria-label={label}
>
<slot />
{#if resizable}
Expand Down
30 changes: 22 additions & 8 deletions js/core/src/Blocks.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -445,17 +445,21 @@
</svelte:head>

<div class="wrap" style:min-height={app_mode ? "100%" : "auto"}>
<div
<main
class="contain"
style:flex-grow={app_mode ? "1" : "auto"}
bind:this={root_container}
style:margin-right={vibe_mode ? `${vibe_editor_width}px` : "0"}
aria-label={title || "Gradio app"}
>
<MountComponents node={app_tree.root} />
</div>
</main>

{#if footer_links.length > 0}
<footer bind:clientHeight={footer_height}>
<footer
bind:clientHeight={footer_height}
aria-label="Gradio footer navigation"
>
{#if footer_links.includes("api")}
<button
on:click={() => {
Expand Down Expand Up @@ -537,7 +541,12 @@
{/if}

{#if api_docs_visible && app_tree.root && ApiDocs}
<div class="api-docs">
<div
class="api-docs"
role="dialog"
aria-modal="true"
aria-label={$reactive_formatter("errors.use_via_api")}
>
<!-- TODO: fix -->
<!-- svelte-ignore a11y-click-events-have-key-events-->
<!-- svelte-ignore a11y-no-static-element-interactions-->
Expand All @@ -547,7 +556,7 @@
set_api_docs_visible(false);
}}
/>
<div class="api-docs-wrap">
<div class="api-docs-wrap" role="document">
<svelte:component
this={ApiDocs}
root_node={app_tree.root}
Expand All @@ -570,7 +579,12 @@
{/if}

{#if settings_visible && app.config && app_tree.root && Settings}
<div class="api-docs">
<div
class="api-docs"
role="dialog"
aria-modal="true"
aria-label={$reactive_formatter("common.settings")}
>
<!-- TODO: fix -->
<!-- svelte-ignore a11y-click-events-have-key-events-->
<!-- svelte-ignore a11y-no-static-element-interactions-->
Expand All @@ -580,7 +594,7 @@
set_settings_visible(false);
}}
/>
<div class="api-docs-wrap">
<div class="api-docs-wrap" role="document">
<svelte:component
this={Settings}
bind:allow_zoom
Expand Down Expand Up @@ -619,7 +633,7 @@
font-size: var(--body-text-size);
}

.contain {
main.contain {
display: flex;
flex-direction: column;
}
Expand Down
14 changes: 13 additions & 1 deletion js/form/BaseForm.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
<script lang="ts">
let { visible, scale, min_width } = $props();
let {
visible,
scale,
min_width,
label = undefined
}: {
visible: boolean | "hidden";
scale: number | null;
min_width: number;
label?: string;
} = $props();
</script>

<div
Expand All @@ -8,6 +18,8 @@
class:hidden-css={visible === "hidden"}
style:flex-grow={scale}
style:min-width={`calc(min(${min_width}px, 100%))`}
role="group"
aria-label={label}
>
<slot />
</div>
Expand Down
65 changes: 65 additions & 0 deletions js/form/BaseForm.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { test, describe, assert, afterEach, beforeEach } from "vitest";
import { tick, mount, unmount } from "svelte";

import BaseForm from "./BaseForm.svelte";

describe("BaseForm Accessibility", () => {
let target: HTMLElement;
let component: any;

beforeEach(() => {
target = document.body.appendChild(document.createElement("div"));
});

afterEach(() => {
if (component) {
unmount(component);
}
if (target.parentNode) {
target.parentNode.removeChild(target);
}
});

test("renders with role='group' for accessibility", async () => {
component = mount(BaseForm, {
target,
props: {
visible: true,
scale: 1,
min_width: 0
}
});
await tick();
const form = target.querySelector(".form");
assert.equal(form?.getAttribute("role"), "group");
});

test("renders with aria-label when label prop is provided", async () => {
component = mount(BaseForm, {
target,
props: {
visible: true,
scale: 1,
min_width: 0,
label: "Form Section"
}
});
await tick();
const form = target.querySelector(".form");
assert.equal(form?.getAttribute("aria-label"), "Form Section");
});

test("renders without aria-label when label is not provided", async () => {
component = mount(BaseForm, {
target,
props: {
visible: true,
scale: 1,
min_width: 0
}
});
await tick();
const form = target.querySelector(".form");
assert.isNull(form?.getAttribute("aria-label"));
});
});