@gradio/tabs 0.5.11 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @gradio/tabs
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Features
6
+
7
+ - [#13467](https://github.com/gradio-app/gradio/pull/13467) [`feba2e1`](https://github.com/gradio-app/gradio/commit/feba2e1e41c68f4bbdacf4c963d8d9689d4b3346) - `Tab`, `TabItem`, `Plot` Unit tests. Thanks @dawoodkhan82!
8
+
3
9
  ## 0.5.11
4
10
 
5
11
  ### Fixes
package/Tabs.test.ts ADDED
@@ -0,0 +1,269 @@
1
+ import { test, describe, afterEach, expect } from "vitest";
2
+ import { cleanup, render, fireEvent } from "@self/tootils/render";
3
+ import { run_shared_prop_tests } from "@self/tootils/shared-prop-tests";
4
+ import type { Tab } from "./shared/Tabs.svelte";
5
+
6
+ import Tabs from "./Index.svelte";
7
+ import TabsWithChild from "./WithChild.svelte";
8
+
9
+ const make_tab = (over: Partial<Tab>): Tab => ({
10
+ label: "Tab",
11
+ id: "t1",
12
+ elem_id: undefined,
13
+ visible: true,
14
+ interactive: true,
15
+ scale: null,
16
+ component_id: 1,
17
+ ...over
18
+ });
19
+
20
+ const tabs: Tab[] = [
21
+ make_tab({ label: "First", id: "t1", component_id: 1 }),
22
+ make_tab({ label: "Second", id: "t2", component_id: 2 }),
23
+ make_tab({ label: "Third", id: "t3", component_id: 3 })
24
+ ];
25
+
26
+ const default_props = {
27
+ initial_tabs: tabs,
28
+ selected: "t1",
29
+ name: "tabs" as const,
30
+ visible: true
31
+ };
32
+
33
+ run_shared_prop_tests({
34
+ component: Tabs,
35
+ name: "Tabs",
36
+ base_props: { initial_tabs: tabs, selected: "t1", name: "tabs" },
37
+ has_label: false,
38
+ has_validation_error: false,
39
+ has_block_wrapper: false,
40
+ visible_false_hides: true
41
+ });
42
+
43
+ describe("Tabs", () => {
44
+ afterEach(() => cleanup());
45
+
46
+ test("renders a tab button for each visible tab", async () => {
47
+ const { getByRole } = await render(Tabs, default_props);
48
+
49
+ expect(getByRole("tab", { name: "First" })).toBeInTheDocument();
50
+ expect(getByRole("tab", { name: "Second" })).toBeInTheDocument();
51
+ expect(getByRole("tab", { name: "Third" })).toBeInTheDocument();
52
+ });
53
+
54
+ test("does not render a button for a tab with visible: false", async () => {
55
+ const { queryByRole } = await render(Tabs, {
56
+ ...default_props,
57
+ initial_tabs: [
58
+ make_tab({ label: "Shown", id: "t1", component_id: 1 }),
59
+ make_tab({ label: "Gone", id: "t2", visible: false, component_id: 2 })
60
+ ]
61
+ });
62
+
63
+ expect(queryByRole("tab", { name: "Gone" })).toBeNull();
64
+ expect(queryByRole("tab", { name: "Shown" })).toBeInTheDocument();
65
+ });
66
+
67
+ test("the selected tab is marked aria-selected", async () => {
68
+ const { getByRole } = await render(Tabs, {
69
+ ...default_props,
70
+ selected: "t2"
71
+ });
72
+
73
+ expect(getByRole("tab", { name: "Second" })).toHaveAttribute(
74
+ "aria-selected",
75
+ "true"
76
+ );
77
+ expect(getByRole("tab", { name: "First" })).toHaveAttribute(
78
+ "aria-selected",
79
+ "false"
80
+ );
81
+ });
82
+ });
83
+
84
+ describe("Props: interactive", () => {
85
+ afterEach(() => cleanup());
86
+
87
+ test("a non-interactive tab is disabled", async () => {
88
+ const { getByRole } = await render(Tabs, {
89
+ ...default_props,
90
+ initial_tabs: [
91
+ make_tab({ label: "Active", id: "t1", component_id: 1 }),
92
+ make_tab({
93
+ label: "Locked",
94
+ id: "t2",
95
+ interactive: false,
96
+ component_id: 2
97
+ })
98
+ ]
99
+ });
100
+
101
+ expect(getByRole("tab", { name: "Locked" })).toBeDisabled();
102
+ expect(getByRole("tab", { name: "Active" })).toBeEnabled();
103
+ });
104
+ });
105
+
106
+ describe("Events: select", () => {
107
+ afterEach(() => cleanup());
108
+
109
+ test("clicking a tab dispatches select with its details", async () => {
110
+ const { listen, getByRole } = await render(Tabs, default_props);
111
+
112
+ const select = listen("select");
113
+
114
+ await fireEvent.click(getByRole("tab", { name: "Second" }));
115
+
116
+ expect(select).toHaveBeenCalledTimes(1);
117
+ expect(select).toHaveBeenCalledWith({
118
+ value: "Second",
119
+ index: 1,
120
+ id: "t2",
121
+ component_id: 2
122
+ });
123
+ });
124
+
125
+ test("clicking the already-selected tab does not dispatch select", async () => {
126
+ const { listen, getByRole } = await render(Tabs, default_props);
127
+
128
+ const select = listen("select");
129
+
130
+ await fireEvent.click(getByRole("tab", { name: "First" }));
131
+
132
+ expect(select).not.toHaveBeenCalled();
133
+ });
134
+
135
+ test("clicking a disabled tab does not make it the selected tab", async () => {
136
+ const { listen, get_data, getByRole } = await render(Tabs, {
137
+ ...default_props,
138
+ selected: "t1",
139
+ initial_tabs: [
140
+ make_tab({ label: "Active", id: "t1", component_id: 1 }),
141
+ make_tab({
142
+ label: "Locked",
143
+ id: "t2",
144
+ interactive: false,
145
+ component_id: 2
146
+ })
147
+ ]
148
+ });
149
+
150
+ const change = listen("change");
151
+
152
+ await fireEvent.click(getByRole("tab", { name: "Locked" }));
153
+
154
+ expect(change).not.toHaveBeenCalled();
155
+ expect(getByRole("tab", { name: "Locked" })).toHaveAttribute(
156
+ "aria-selected",
157
+ "false"
158
+ );
159
+ expect((await get_data()).selected).toBe("t1");
160
+ });
161
+ });
162
+
163
+ describe("Events: change", () => {
164
+ afterEach(() => cleanup());
165
+
166
+ test("clicking a different tab dispatches change once", async () => {
167
+ const { listen, getByRole } = await render(Tabs, default_props);
168
+
169
+ const change = listen("change");
170
+
171
+ await fireEvent.click(getByRole("tab", { name: "Third" }));
172
+
173
+ expect(change).toHaveBeenCalledTimes(1);
174
+ });
175
+
176
+ test("clicking the already-selected tab does not dispatch change", async () => {
177
+ const { listen, getByRole } = await render(Tabs, default_props);
178
+
179
+ const change = listen("change");
180
+
181
+ await fireEvent.click(getByRole("tab", { name: "First" }));
182
+
183
+ expect(change).not.toHaveBeenCalled();
184
+ });
185
+ });
186
+
187
+ describe("Events: gradio_tab_select", () => {
188
+ afterEach(() => cleanup());
189
+
190
+ test("fires when the selected tab changes via set_data", async () => {
191
+ const { listen, set_data } = await render(Tabs, default_props);
192
+
193
+ const gradio_tab_select = listen("gradio_tab_select");
194
+
195
+ await set_data({ selected: "t3" });
196
+
197
+ expect(gradio_tab_select).toHaveBeenCalledTimes(1);
198
+ expect(gradio_tab_select).toHaveBeenCalledWith({
199
+ value: "Third",
200
+ index: 2,
201
+ id: "t3",
202
+ component_id: 3
203
+ });
204
+ });
205
+ });
206
+
207
+ describe("get_data / set_data", () => {
208
+ afterEach(() => cleanup());
209
+
210
+ test("get_data returns the initial selection", async () => {
211
+ const { get_data } = await render(Tabs, default_props);
212
+
213
+ const data = await get_data();
214
+ expect(data.selected).toBe("t1");
215
+ });
216
+
217
+ test("clicking a tab updates the selection returned by get_data", async () => {
218
+ const { get_data, getByRole } = await render(Tabs, default_props);
219
+
220
+ await fireEvent.click(getByRole("tab", { name: "Second" }));
221
+
222
+ const data = await get_data();
223
+ expect(data.selected).toBe("t2");
224
+ });
225
+
226
+ test("set_data updates which tab is marked aria-selected", async () => {
227
+ const { set_data, getByRole } = await render(Tabs, default_props);
228
+
229
+ await set_data({ selected: "t3" });
230
+
231
+ expect(getByRole("tab", { name: "Third" })).toHaveAttribute(
232
+ "aria-selected",
233
+ "true"
234
+ );
235
+ });
236
+
237
+ test("set_data then get_data round-trips the selection", async () => {
238
+ const { set_data, get_data } = await render(Tabs, default_props);
239
+
240
+ await set_data({ selected: "t2" });
241
+
242
+ const data = await get_data();
243
+ expect(data.selected).toBe("t2");
244
+ });
245
+ });
246
+
247
+ describe("Children / slot", () => {
248
+ afterEach(() => cleanup());
249
+
250
+ test("renders slot children inside the tabs container", async () => {
251
+ const { getByTestId } = await render(TabsWithChild, default_props);
252
+
253
+ expect(getByTestId("slot-content")).toBeVisible();
254
+ });
255
+ });
256
+
257
+ describe("Edge cases", () => {
258
+ afterEach(() => cleanup());
259
+
260
+ test("no select or change events fire on initial mount", async () => {
261
+ const { listen } = await render(Tabs, default_props);
262
+
263
+ const select = listen("select", { retrospective: true });
264
+ const change = listen("change", { retrospective: true });
265
+
266
+ expect(select).not.toHaveBeenCalled();
267
+ expect(change).not.toHaveBeenCalled();
268
+ });
269
+ });
@@ -0,0 +1,8 @@
1
+ <script lang="ts">
2
+ import Tabs from "./Index.svelte";
3
+ let props = $props();
4
+ </script>
5
+
6
+ <Tabs {...props}>
7
+ <div data-testid="slot-content">tab panel content</div>
8
+ </Tabs>
@@ -0,0 +1,8 @@
1
+ <script lang="ts">
2
+ import Tabs from "./Index.svelte";
3
+ let props = $props();
4
+ </script>
5
+
6
+ <Tabs {...props}>
7
+ <div data-testid="slot-content">tab panel content</div>
8
+ </Tabs>
@@ -0,0 +1,3 @@
1
+ declare const WithChild: import("svelte").Component<$$ComponentProps, {}, "">;
2
+ type WithChild = ReturnType<typeof WithChild>;
3
+ export default WithChild;
@@ -142,6 +142,8 @@
142
142
  await tick();
143
143
  await new Promise((r) => requestAnimationFrame(r));
144
144
 
145
+ if (!tab_nav_el) return;
146
+
145
147
  const available = tab_nav_el.clientWidth;
146
148
 
147
149
  let cumulative = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/tabs",
3
- "version": "0.5.11",
3
+ "version": "0.6.0",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -142,6 +142,8 @@
142
142
  await tick();
143
143
  await new Promise((r) => requestAnimationFrame(r));
144
144
 
145
+ if (!tab_nav_el) return;
146
+
145
147
  const available = tab_nav_el.clientWidth;
146
148
 
147
149
  let cumulative = 0;