@gradio/core 1.4.0 → 1.4.1
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 +26 -0
- package/dist/src/init.svelte.d.ts +1 -0
- package/dist/src/init.svelte.js +18 -3
- package/dist/src/init.test.skip.d.ts +1 -0
- package/dist/src/init.test.skip.js +627 -0
- package/package.json +44 -44
- package/src/i18n.test.ts +28 -2
- package/src/init.svelte.ts +21 -4
- package/src/{init.test.ts → init.test.skip.ts} +303 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @gradio/core
|
|
2
2
|
|
|
3
|
+
## 1.4.1
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- [#12566](https://github.com/gradio-app/gradio/pull/12566) [`7760161`](https://github.com/gradio-app/gradio/commit/7760161258abe6329b754dd6d2511fc3b61fed95) - Fix custom components in SSR Mode + Custom Component Examples. Thanks @freddyaboulton!
|
|
8
|
+
- [#12803](https://github.com/gradio-app/gradio/pull/12803) [`f4c3a6d`](https://github.com/gradio-app/gradio/commit/f4c3a6dcb45218722d3150baef953c731d3eccf2) - fix: gradio_api path in mount_gradio_app. Thanks @shandowc!
|
|
9
|
+
|
|
10
|
+
### Dependency updates
|
|
11
|
+
|
|
12
|
+
- @gradio/utils@0.12.1
|
|
13
|
+
- @gradio/statustracker@0.13.0
|
|
14
|
+
- @gradio/gallery@0.17.4
|
|
15
|
+
- @gradio/plot@0.10.6
|
|
16
|
+
- @gradio/textbox@0.13.6
|
|
17
|
+
- @gradio/html@0.12.0
|
|
18
|
+
- @gradio/button@0.6.6
|
|
19
|
+
- @gradio/code@0.17.5
|
|
20
|
+
- @gradio/paramviewer@0.9.6
|
|
21
|
+
- @gradio/checkbox@0.6.5
|
|
22
|
+
- @gradio/image@0.26.0
|
|
23
|
+
- @gradio/video@0.20.5
|
|
24
|
+
- @gradio/file@0.14.5
|
|
25
|
+
- @gradio/audio@0.23.0
|
|
26
|
+
- @gradio/column@0.3.2
|
|
27
|
+
- @gradio/dropdown@0.11.6
|
|
28
|
+
|
|
3
29
|
## 1.4.0
|
|
4
30
|
|
|
5
31
|
### Features
|
package/dist/src/init.svelte.js
CHANGED
|
@@ -8,6 +8,19 @@ const type_map = {
|
|
|
8
8
|
walkthrough: "tabs",
|
|
9
9
|
walkthroughstep: "tabitem"
|
|
10
10
|
};
|
|
11
|
+
export function get_api_url(config) {
|
|
12
|
+
// Handle api_prefix correctly when app is mounted at a subpath.
|
|
13
|
+
// config.root may not include a trailing slash, so we normalize its pathname
|
|
14
|
+
// before appending api_prefix to ensure correct URL construction.
|
|
15
|
+
const rootUrl = new URL(config.root);
|
|
16
|
+
const rootPath = rootUrl.pathname.endsWith("/")
|
|
17
|
+
? rootUrl.pathname
|
|
18
|
+
: rootUrl.pathname + "/";
|
|
19
|
+
const apiPrefix = config.api_prefix.startsWith("/")
|
|
20
|
+
? config.api_prefix
|
|
21
|
+
: "/" + config.api_prefix;
|
|
22
|
+
return new URL(rootPath.slice(0, -1) + apiPrefix, rootUrl.origin).toString();
|
|
23
|
+
}
|
|
11
24
|
export class AppTree {
|
|
12
25
|
/** the raw component structure received from the backend */
|
|
13
26
|
#component_payload;
|
|
@@ -45,9 +58,10 @@ export class AppTree {
|
|
|
45
58
|
this.ready_resolve = resolve;
|
|
46
59
|
});
|
|
47
60
|
this.reactive_formatter = reactive_formatter;
|
|
61
|
+
const api_url = get_api_url(config);
|
|
48
62
|
this.#config = {
|
|
49
63
|
...config,
|
|
50
|
-
api_url
|
|
64
|
+
api_url
|
|
51
65
|
};
|
|
52
66
|
this.#component_payload = components;
|
|
53
67
|
this.#layout_payload = layout;
|
|
@@ -77,9 +91,10 @@ export class AppTree {
|
|
|
77
91
|
reload(components, layout, dependencies, config) {
|
|
78
92
|
this.#layout_payload = layout;
|
|
79
93
|
this.#component_payload = components;
|
|
94
|
+
const api_url = get_api_url(config);
|
|
80
95
|
this.#config = {
|
|
81
96
|
...config,
|
|
82
|
-
api_url
|
|
97
|
+
api_url
|
|
83
98
|
};
|
|
84
99
|
this.#dependency_payload = dependencies;
|
|
85
100
|
this.root = this.create_node({ id: layout.id, children: [] }, new Map(), true);
|
|
@@ -469,7 +484,7 @@ function gather_props(id, props, dependencies, client, api_url, additional = {})
|
|
|
469
484
|
_shared_props.client = client;
|
|
470
485
|
_shared_props.id = id;
|
|
471
486
|
_shared_props.interactive = determine_interactivity(id, _shared_props.interactive, _props.value, dependencies);
|
|
472
|
-
_shared_props.load_component = (name, variant) => get_component(name, "", api_url, variant)
|
|
487
|
+
_shared_props.load_component = (name, variant, component_class_id) => get_component(name, component_class_id || "", api_url, variant);
|
|
473
488
|
_shared_props.visible =
|
|
474
489
|
_shared_props.visible === undefined ? true : _shared_props.visible;
|
|
475
490
|
_shared_props.loading_status = {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import { describe, test, expect, vi } from "vitest";
|
|
2
|
+
import { spy } from "tinyspy";
|
|
3
|
+
import { setupWorker } from "msw/browser";
|
|
4
|
+
import { http, HttpResponse } from "msw";
|
|
5
|
+
import { Dependency, TargetMap } from "./types";
|
|
6
|
+
import { process_frontend_fn, create_target_meta, determine_interactivity, process_server_fn, get_component } from "./_init";
|
|
7
|
+
import { get_api_url } from "./init.svelte";
|
|
8
|
+
import { commands } from "@vitest/browser/context";
|
|
9
|
+
describe("process_frontend_fn", () => {
|
|
10
|
+
test("empty source code returns null", () => {
|
|
11
|
+
const source = "";
|
|
12
|
+
const fn = process_frontend_fn(source, false, 1, 1);
|
|
13
|
+
expect(fn).toBe(null);
|
|
14
|
+
});
|
|
15
|
+
test("falsey source code returns null: false", () => {
|
|
16
|
+
const source = false;
|
|
17
|
+
const fn = process_frontend_fn(source, false, 1, 1);
|
|
18
|
+
expect(fn).toBe(null);
|
|
19
|
+
});
|
|
20
|
+
test("falsey source code returns null: undefined", () => {
|
|
21
|
+
const source = undefined;
|
|
22
|
+
const fn = process_frontend_fn(source, false, 1, 1);
|
|
23
|
+
expect(fn).toBe(null);
|
|
24
|
+
});
|
|
25
|
+
test("falsey source code returns null: null", () => {
|
|
26
|
+
const source = null;
|
|
27
|
+
const fn = process_frontend_fn(source, false, 1, 1);
|
|
28
|
+
expect(fn).toBe(null);
|
|
29
|
+
});
|
|
30
|
+
test("source code returns a function", () => {
|
|
31
|
+
const source = "(arg) => arg";
|
|
32
|
+
const fn = process_frontend_fn(source, false, 1, 1);
|
|
33
|
+
expect(typeof fn).toBe("function");
|
|
34
|
+
});
|
|
35
|
+
test("arrays of values can be passed to the generated function", async () => {
|
|
36
|
+
const source = "(arg) => arg";
|
|
37
|
+
const fn = process_frontend_fn(source, false, 1, 1);
|
|
38
|
+
if (fn) {
|
|
39
|
+
await expect(fn([1])).resolves.toEqual([1]);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
test("arrays of many values can be passed", async () => {
|
|
43
|
+
const source = "(...args) => args";
|
|
44
|
+
const fn = process_frontend_fn(source, false, 1, 1);
|
|
45
|
+
if (fn) {
|
|
46
|
+
await expect(fn([1, 2, 3, 4, 5, 6])).resolves.toEqual([1, 2, 3, 4, 5, 6]);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
test("The generated function returns a promise", () => {
|
|
50
|
+
const source = "(arg) => arg";
|
|
51
|
+
const fn = process_frontend_fn(source, false, 1, 1);
|
|
52
|
+
if (fn) {
|
|
53
|
+
expect(fn([1])).toBeInstanceOf(Promise);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
test("The generated function is callable and returns the expected value", async () => {
|
|
57
|
+
const source = "(arg) => arg";
|
|
58
|
+
const fn = process_frontend_fn(source, false, 1, 1);
|
|
59
|
+
if (fn) {
|
|
60
|
+
await expect(fn([1])).resolves.toEqual([1]);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
test("The return value of the function is wrapped in an array if there is no backend function and the input length is 1", async () => {
|
|
64
|
+
const source = "(arg) => arg";
|
|
65
|
+
const fn = process_frontend_fn(source, false, 1, 1);
|
|
66
|
+
if (fn) {
|
|
67
|
+
await expect(fn([1])).resolves.toEqual([1]);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
test("The return value of the function is not wrapped in an array if there is no backend function and the input length is greater than 1", async () => {
|
|
71
|
+
const source = "(arg) => arg";
|
|
72
|
+
const fn = process_frontend_fn(source, false, 2, 2);
|
|
73
|
+
if (fn) {
|
|
74
|
+
await expect(fn([1])).resolves.toEqual(1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
test("The return value of the function is wrapped in an array if there is a backend function and the input length is 1", async () => {
|
|
78
|
+
const source = "(arg) => arg";
|
|
79
|
+
const fn = process_frontend_fn(source, true, 1, 1);
|
|
80
|
+
if (fn) {
|
|
81
|
+
await expect(fn([1])).resolves.toEqual([1]);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
test("The return value of the function is not wrapped in an array if there is a backend function and the input length is greater than 1", async () => {
|
|
85
|
+
const source = "(arg) => arg";
|
|
86
|
+
const fn = process_frontend_fn(source, true, 2, 2);
|
|
87
|
+
if (fn) {
|
|
88
|
+
await expect(fn([1])).resolves.toEqual(1);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe("create_target_meta", () => {
|
|
93
|
+
test("creates a target map", () => {
|
|
94
|
+
const targets = [
|
|
95
|
+
[1, "change"],
|
|
96
|
+
[2, "input"],
|
|
97
|
+
[3, "load"]
|
|
98
|
+
];
|
|
99
|
+
const fn_index = 0;
|
|
100
|
+
const target_map = {};
|
|
101
|
+
const result = create_target_meta(targets, fn_index, target_map);
|
|
102
|
+
expect(result).toEqual({
|
|
103
|
+
1: { change: [0] },
|
|
104
|
+
2: { input: [0] },
|
|
105
|
+
3: { load: [0] }
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
test("if the target already exists, it adds the new trigger to the list", () => {
|
|
109
|
+
const targets = [
|
|
110
|
+
[1, "change"],
|
|
111
|
+
[1, "input"],
|
|
112
|
+
[1, "load"]
|
|
113
|
+
];
|
|
114
|
+
const fn_index = 1;
|
|
115
|
+
const target_map = {
|
|
116
|
+
1: { change: [0] }
|
|
117
|
+
};
|
|
118
|
+
const result = create_target_meta(targets, fn_index, target_map);
|
|
119
|
+
expect(result).toEqual({
|
|
120
|
+
1: { change: [0, 1], input: [1], load: [1] }
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
test("if the trigger already exists, it adds the new function to the list", () => {
|
|
124
|
+
const targets = [
|
|
125
|
+
[1, "change"],
|
|
126
|
+
[2, "change"],
|
|
127
|
+
[3, "change"]
|
|
128
|
+
];
|
|
129
|
+
const fn_index = 1;
|
|
130
|
+
const target_map = {
|
|
131
|
+
1: { change: [0] },
|
|
132
|
+
2: { change: [0] },
|
|
133
|
+
3: { change: [0] }
|
|
134
|
+
};
|
|
135
|
+
const result = create_target_meta(targets, fn_index, target_map);
|
|
136
|
+
expect(result).toEqual({
|
|
137
|
+
1: { change: [0, 1] },
|
|
138
|
+
2: { change: [0, 1] },
|
|
139
|
+
3: { change: [0, 1] }
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
test("if the target and trigger already exist, it adds the new function to the list", () => {
|
|
143
|
+
const targets = [[1, "change"]];
|
|
144
|
+
const fn_index = 1;
|
|
145
|
+
const target_map = {
|
|
146
|
+
1: { change: [0] }
|
|
147
|
+
};
|
|
148
|
+
const result = create_target_meta(targets, fn_index, target_map);
|
|
149
|
+
expect(result).toEqual({
|
|
150
|
+
1: { change: [0, 1] }
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
test("if the target, trigger and function id already exist, it does not add duplicates", () => {
|
|
154
|
+
const targets = [[1, "change"]];
|
|
155
|
+
const fn_index = 0;
|
|
156
|
+
const target_map = {
|
|
157
|
+
1: { change: [0] }
|
|
158
|
+
};
|
|
159
|
+
const result = create_target_meta(targets, fn_index, target_map);
|
|
160
|
+
expect(result).toEqual({
|
|
161
|
+
1: { change: [0] }
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
describe("determine_interactivity", () => {
|
|
166
|
+
test("returns true if the prop is interactive = true", () => {
|
|
167
|
+
const result = determine_interactivity(0, true, "hi", new Set([0]), new Set([2]));
|
|
168
|
+
expect(result).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
test("returns false if the prop is interactive = false", () => {
|
|
171
|
+
const result = determine_interactivity(0, false, "hi", new Set([0]), new Set([2]));
|
|
172
|
+
expect(result).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
test("returns true if the component is an input", () => {
|
|
175
|
+
const result = determine_interactivity(0, undefined, "hi", new Set([0]), new Set([2]));
|
|
176
|
+
expect(result).toBe(true);
|
|
177
|
+
});
|
|
178
|
+
test("returns true if the component is not an input or output and the component has no default value: empty string", () => {
|
|
179
|
+
const result = determine_interactivity(2, undefined, "", new Set([0]), new Set([1]));
|
|
180
|
+
expect(result).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
test("returns true if the component is not an input or output and the component has no default value: empty array", () => {
|
|
183
|
+
const result = determine_interactivity(2, undefined, [], new Set([0]), new Set([1]));
|
|
184
|
+
expect(result).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
test("returns true if the component is not an input or output and the component has no default value: boolean", () => {
|
|
187
|
+
const result = determine_interactivity(2, undefined, false, new Set([0]), new Set([1]));
|
|
188
|
+
expect(result).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
test("returns true if the component is not an input or output and the component has no default value: undefined", () => {
|
|
191
|
+
const result = determine_interactivity(2, undefined, undefined, new Set([0]), new Set([1]));
|
|
192
|
+
expect(result).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
test("returns true if the component is not an input or output and the component has no default value: null", () => {
|
|
195
|
+
const result = determine_interactivity(2, undefined, null, new Set([0]), new Set([1]));
|
|
196
|
+
expect(result).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
test("returns true if the component is not an input or output and the component has no default value: 0", () => {
|
|
199
|
+
const result = determine_interactivity(2, undefined, 0, new Set([0]), new Set([1]));
|
|
200
|
+
expect(result).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
test("returns false if the component is not an input or output and the component has a default value", () => {
|
|
203
|
+
const result = determine_interactivity(2, undefined, "hello", new Set([0]), new Set([1]));
|
|
204
|
+
expect(result).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
describe("process_server_fn", () => {
|
|
208
|
+
test("returns an object", () => {
|
|
209
|
+
const result = process_server_fn(1, ["fn1", "fn2"], {});
|
|
210
|
+
expect(result).toBeTypeOf("object");
|
|
211
|
+
});
|
|
212
|
+
test("returns an object with the correct keys", () => {
|
|
213
|
+
const result = process_server_fn(1, ["fn1", "fn2"], {});
|
|
214
|
+
expect(Object.keys(result)).toEqual(["fn1", "fn2"]);
|
|
215
|
+
});
|
|
216
|
+
test("returns an object with the correct keys and values", () => {
|
|
217
|
+
const app = {
|
|
218
|
+
component_server: async (id, fn, args) => {
|
|
219
|
+
return args;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const result = process_server_fn(1, ["fn1", "fn2"], app);
|
|
223
|
+
expect(Object.keys(result)).toEqual(["fn1", "fn2"]);
|
|
224
|
+
expect(result.fn1).toBeInstanceOf(Function);
|
|
225
|
+
expect(result.fn2).toBeInstanceOf(Function);
|
|
226
|
+
});
|
|
227
|
+
test("returned server functions should resolve to a promise", async () => {
|
|
228
|
+
const app = {
|
|
229
|
+
component_server: async (id, fn, args) => {
|
|
230
|
+
return args;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
const result = process_server_fn(1, ["fn1", "fn2"], app);
|
|
234
|
+
const response = result.fn1("hello");
|
|
235
|
+
expect(response).toBeInstanceOf(Promise);
|
|
236
|
+
});
|
|
237
|
+
test("the functions call the clients component_server function with the correct arguments ", async () => {
|
|
238
|
+
const mock = spy(async (id, fn, args) => {
|
|
239
|
+
return args;
|
|
240
|
+
});
|
|
241
|
+
const app = {
|
|
242
|
+
component_server: mock
|
|
243
|
+
};
|
|
244
|
+
const result = process_server_fn(1, ["fn1", "fn2"], app);
|
|
245
|
+
const response = await result.fn1("hello");
|
|
246
|
+
expect(response).toBe("hello");
|
|
247
|
+
expect(mock.calls).toEqual([[1, "fn1", "hello"]]);
|
|
248
|
+
});
|
|
249
|
+
test("if there are no server functions, it returns an empty object", () => {
|
|
250
|
+
const result = process_server_fn(1, undefined, {});
|
|
251
|
+
expect(result).toEqual({});
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
describe("get_component", () => {
|
|
255
|
+
test("returns an object", () => {
|
|
256
|
+
const result = get_component("test-component-one", "class_id", "root", []);
|
|
257
|
+
expect(result.component).toBeTypeOf("object");
|
|
258
|
+
});
|
|
259
|
+
test("returns an object with the correct keys", () => {
|
|
260
|
+
const result = get_component("test-component-one", "class_id", "root", []);
|
|
261
|
+
expect(Object.keys(result)).toEqual([
|
|
262
|
+
"component",
|
|
263
|
+
"name",
|
|
264
|
+
"example_components"
|
|
265
|
+
]);
|
|
266
|
+
});
|
|
267
|
+
test("the component key is a promise", () => {
|
|
268
|
+
const result = get_component("test-component-one", "class_id", "root", []);
|
|
269
|
+
expect(result.component).toBeInstanceOf(Promise);
|
|
270
|
+
});
|
|
271
|
+
test("the resolved component key is an object", async () => {
|
|
272
|
+
const result = get_component("test-component-one", "class_id", "root", []);
|
|
273
|
+
const o = await result.component;
|
|
274
|
+
expect(o).toBeTypeOf("object");
|
|
275
|
+
});
|
|
276
|
+
test("getting the same component twice should return the same promise", () => {
|
|
277
|
+
const result = get_component("test-component-one", "class_id", "root", []);
|
|
278
|
+
const result_two = get_component("test-component-one", "class_id", "root", []);
|
|
279
|
+
expect(result.component).toBe(result_two.component);
|
|
280
|
+
});
|
|
281
|
+
test("if example components are not provided, the example_components key is undefined", async () => {
|
|
282
|
+
const result = get_component("dataset", "class_id", "root", []);
|
|
283
|
+
expect(result.example_components).toBe(undefined);
|
|
284
|
+
});
|
|
285
|
+
test("if the type is not a dataset, the example_components key is undefined", async () => {
|
|
286
|
+
const result = get_component("test-component-one", "class_id", "root", []);
|
|
287
|
+
expect(result.example_components).toBe(undefined);
|
|
288
|
+
});
|
|
289
|
+
test("when the type is a dataset, returns an object with the correct keys and values and example components", () => {
|
|
290
|
+
const result = get_component("dataset", "class_id", "root", [
|
|
291
|
+
{
|
|
292
|
+
type: "test-component-one",
|
|
293
|
+
component_class_id: "example_class_id",
|
|
294
|
+
id: 1,
|
|
295
|
+
props: {
|
|
296
|
+
value: "hi",
|
|
297
|
+
interactive: false
|
|
298
|
+
},
|
|
299
|
+
has_modes: false,
|
|
300
|
+
instance: {},
|
|
301
|
+
component: {}
|
|
302
|
+
}
|
|
303
|
+
], ["test-component-one"]);
|
|
304
|
+
expect(result.component).toBeTypeOf("object");
|
|
305
|
+
expect(result.example_components).toBeInstanceOf(Map);
|
|
306
|
+
});
|
|
307
|
+
test("when example components are returned, returns an object with the correct keys and values and example components", () => {
|
|
308
|
+
const result = get_component("dataset", "class_id", "root", [
|
|
309
|
+
{
|
|
310
|
+
type: "test-component-one",
|
|
311
|
+
component_class_id: "example_class_id",
|
|
312
|
+
id: 1,
|
|
313
|
+
props: {
|
|
314
|
+
value: "hi",
|
|
315
|
+
interactive: false
|
|
316
|
+
},
|
|
317
|
+
key: "test-component-one",
|
|
318
|
+
has_modes: false,
|
|
319
|
+
instance: {},
|
|
320
|
+
component: {}
|
|
321
|
+
}
|
|
322
|
+
], ["test-component-one"]);
|
|
323
|
+
expect(result.example_components?.get("test-component-one")).toBeTypeOf("object");
|
|
324
|
+
expect(result.example_components?.get("test-component-one")).toBeInstanceOf(Promise);
|
|
325
|
+
});
|
|
326
|
+
test.skip("if the component is not found then it should request the component from the server", async () => {
|
|
327
|
+
const api_url = "example.com";
|
|
328
|
+
const id = "test-random";
|
|
329
|
+
const variant = "component";
|
|
330
|
+
const handlers = [
|
|
331
|
+
http.get(`${api_url}/custom_component/${id}/client/${variant}/style.css`, () => {
|
|
332
|
+
return new HttpResponse('console.log("boo")', {
|
|
333
|
+
status: 200,
|
|
334
|
+
headers: {
|
|
335
|
+
"Content-Type": "text/css"
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
})
|
|
339
|
+
];
|
|
340
|
+
// vi.mock calls are always hoisted out of the test function to the top of the file
|
|
341
|
+
// so we need to use vi.hoisted to hoist the mock function above the vi.mock call
|
|
342
|
+
const { mock } = vi.hoisted(() => {
|
|
343
|
+
return { mock: vi.fn() };
|
|
344
|
+
});
|
|
345
|
+
vi.mock(`example.com/custom_component/test-random/client/component/index.js`, async () => {
|
|
346
|
+
mock();
|
|
347
|
+
return {
|
|
348
|
+
default: {
|
|
349
|
+
default: "HELLO"
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
const worker = setupWorker(...handlers);
|
|
354
|
+
worker.start();
|
|
355
|
+
await get_component("test-random", id, api_url, []).component;
|
|
356
|
+
expect(mock).toHaveBeenCalled();
|
|
357
|
+
worker.stop();
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
describe("get_api_url", () => {
|
|
361
|
+
describe("root URL with trailing slash", () => {
|
|
362
|
+
test("root with trailing slash, api_prefix with leading slash", () => {
|
|
363
|
+
const config = {
|
|
364
|
+
root: "http://example.com/myapp/",
|
|
365
|
+
api_prefix: "/api",
|
|
366
|
+
theme: "default",
|
|
367
|
+
version: "1.0.0",
|
|
368
|
+
autoscroll: true
|
|
369
|
+
};
|
|
370
|
+
const result = get_api_url(config);
|
|
371
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
372
|
+
});
|
|
373
|
+
test("root with trailing slash, api_prefix without leading slash", () => {
|
|
374
|
+
const config = {
|
|
375
|
+
root: "http://example.com/myapp/",
|
|
376
|
+
api_prefix: "api",
|
|
377
|
+
theme: "default",
|
|
378
|
+
version: "1.0.0",
|
|
379
|
+
autoscroll: true
|
|
380
|
+
};
|
|
381
|
+
const result = get_api_url(config);
|
|
382
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
383
|
+
});
|
|
384
|
+
test("root at domain root with trailing slash", () => {
|
|
385
|
+
const config = {
|
|
386
|
+
root: "http://example.com/",
|
|
387
|
+
api_prefix: "/api",
|
|
388
|
+
theme: "default",
|
|
389
|
+
version: "1.0.0",
|
|
390
|
+
autoscroll: true
|
|
391
|
+
};
|
|
392
|
+
const result = get_api_url(config);
|
|
393
|
+
expect(result).toBe("http://example.com/api");
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
describe("root URL without trailing slash", () => {
|
|
397
|
+
test("root without trailing slash, api_prefix with leading slash", () => {
|
|
398
|
+
const config = {
|
|
399
|
+
root: "http://example.com/myapp",
|
|
400
|
+
api_prefix: "/api",
|
|
401
|
+
theme: "default",
|
|
402
|
+
version: "1.0.0",
|
|
403
|
+
autoscroll: true
|
|
404
|
+
};
|
|
405
|
+
const result = get_api_url(config);
|
|
406
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
407
|
+
});
|
|
408
|
+
test("root without trailing slash, api_prefix without leading slash", () => {
|
|
409
|
+
const config = {
|
|
410
|
+
root: "http://example.com/myapp",
|
|
411
|
+
api_prefix: "api",
|
|
412
|
+
theme: "default",
|
|
413
|
+
version: "1.0.0",
|
|
414
|
+
autoscroll: true
|
|
415
|
+
};
|
|
416
|
+
const result = get_api_url(config);
|
|
417
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
418
|
+
});
|
|
419
|
+
test("root at domain root without trailing slash", () => {
|
|
420
|
+
const config = {
|
|
421
|
+
root: "http://example.com",
|
|
422
|
+
api_prefix: "/api",
|
|
423
|
+
theme: "default",
|
|
424
|
+
version: "1.0.0",
|
|
425
|
+
autoscroll: true
|
|
426
|
+
};
|
|
427
|
+
const result = get_api_url(config);
|
|
428
|
+
expect(result).toBe("http://example.com/api");
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
describe("different root path combinations", () => {
|
|
432
|
+
test("root path is just '/'", () => {
|
|
433
|
+
const config = {
|
|
434
|
+
root: "http://example.com/",
|
|
435
|
+
api_prefix: "/api",
|
|
436
|
+
theme: "default",
|
|
437
|
+
version: "1.0.0",
|
|
438
|
+
autoscroll: true
|
|
439
|
+
};
|
|
440
|
+
const result = get_api_url(config);
|
|
441
|
+
expect(result).toBe("http://example.com/api");
|
|
442
|
+
});
|
|
443
|
+
test("root path is '/' without trailing slash", () => {
|
|
444
|
+
const config = {
|
|
445
|
+
root: "http://example.com",
|
|
446
|
+
api_prefix: "/api",
|
|
447
|
+
theme: "default",
|
|
448
|
+
version: "1.0.0",
|
|
449
|
+
autoscroll: true
|
|
450
|
+
};
|
|
451
|
+
const result = get_api_url(config);
|
|
452
|
+
expect(result).toBe("http://example.com/api");
|
|
453
|
+
});
|
|
454
|
+
test("root path is '/myapp'", () => {
|
|
455
|
+
const config = {
|
|
456
|
+
root: "http://example.com/myapp",
|
|
457
|
+
api_prefix: "/api",
|
|
458
|
+
theme: "default",
|
|
459
|
+
version: "1.0.0",
|
|
460
|
+
autoscroll: true
|
|
461
|
+
};
|
|
462
|
+
const result = get_api_url(config);
|
|
463
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
464
|
+
});
|
|
465
|
+
test("root path is '/myapp/'", () => {
|
|
466
|
+
const config = {
|
|
467
|
+
root: "http://example.com/myapp/",
|
|
468
|
+
api_prefix: "/api",
|
|
469
|
+
theme: "default",
|
|
470
|
+
version: "1.0.0",
|
|
471
|
+
autoscroll: true
|
|
472
|
+
};
|
|
473
|
+
const result = get_api_url(config);
|
|
474
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
475
|
+
});
|
|
476
|
+
test("root path is '/deep/nested/path'", () => {
|
|
477
|
+
const config = {
|
|
478
|
+
root: "http://example.com/deep/nested/path",
|
|
479
|
+
api_prefix: "/api",
|
|
480
|
+
theme: "default",
|
|
481
|
+
version: "1.0.0",
|
|
482
|
+
autoscroll: true
|
|
483
|
+
};
|
|
484
|
+
const result = get_api_url(config);
|
|
485
|
+
expect(result).toBe("http://example.com/deep/nested/path/api");
|
|
486
|
+
});
|
|
487
|
+
test("root path is '/deep/nested/path/'", () => {
|
|
488
|
+
const config = {
|
|
489
|
+
root: "http://example.com/deep/nested/path/",
|
|
490
|
+
api_prefix: "/api",
|
|
491
|
+
theme: "default",
|
|
492
|
+
version: "1.0.0",
|
|
493
|
+
autoscroll: true
|
|
494
|
+
};
|
|
495
|
+
const result = get_api_url(config);
|
|
496
|
+
expect(result).toBe("http://example.com/deep/nested/path/api");
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
describe("different api_prefix formats", () => {
|
|
500
|
+
test("api_prefix with leading slash", () => {
|
|
501
|
+
const config = {
|
|
502
|
+
root: "http://example.com/myapp",
|
|
503
|
+
api_prefix: "/api",
|
|
504
|
+
theme: "default",
|
|
505
|
+
version: "1.0.0",
|
|
506
|
+
autoscroll: true
|
|
507
|
+
};
|
|
508
|
+
const result = get_api_url(config);
|
|
509
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
510
|
+
});
|
|
511
|
+
test("api_prefix without leading slash", () => {
|
|
512
|
+
const config = {
|
|
513
|
+
root: "http://example.com/myapp",
|
|
514
|
+
api_prefix: "api",
|
|
515
|
+
theme: "default",
|
|
516
|
+
version: "1.0.0",
|
|
517
|
+
autoscroll: true
|
|
518
|
+
};
|
|
519
|
+
const result = get_api_url(config);
|
|
520
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
521
|
+
});
|
|
522
|
+
test("api_prefix with nested path and leading slash", () => {
|
|
523
|
+
const config = {
|
|
524
|
+
root: "http://example.com/myapp",
|
|
525
|
+
api_prefix: "/api/v1",
|
|
526
|
+
theme: "default",
|
|
527
|
+
version: "1.0.0",
|
|
528
|
+
autoscroll: true
|
|
529
|
+
};
|
|
530
|
+
const result = get_api_url(config);
|
|
531
|
+
expect(result).toBe("http://example.com/myapp/api/v1");
|
|
532
|
+
});
|
|
533
|
+
test("api_prefix with nested path without leading slash", () => {
|
|
534
|
+
const config = {
|
|
535
|
+
root: "http://example.com/myapp",
|
|
536
|
+
api_prefix: "api/v1",
|
|
537
|
+
theme: "default",
|
|
538
|
+
version: "1.0.0",
|
|
539
|
+
autoscroll: true
|
|
540
|
+
};
|
|
541
|
+
const result = get_api_url(config);
|
|
542
|
+
expect(result).toBe("http://example.com/myapp/api/v1");
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
describe("edge cases", () => {
|
|
546
|
+
test("root with port number", () => {
|
|
547
|
+
const config = {
|
|
548
|
+
root: "http://example.com:8080/myapp",
|
|
549
|
+
api_prefix: "/api",
|
|
550
|
+
theme: "default",
|
|
551
|
+
version: "1.0.0",
|
|
552
|
+
autoscroll: true
|
|
553
|
+
};
|
|
554
|
+
const result = get_api_url(config);
|
|
555
|
+
expect(result).toBe("http://example.com:8080/myapp/api");
|
|
556
|
+
});
|
|
557
|
+
test("root with HTTPS", () => {
|
|
558
|
+
const config = {
|
|
559
|
+
root: "https://example.com/myapp",
|
|
560
|
+
api_prefix: "/api",
|
|
561
|
+
theme: "default",
|
|
562
|
+
version: "1.0.0",
|
|
563
|
+
autoscroll: true
|
|
564
|
+
};
|
|
565
|
+
const result = get_api_url(config);
|
|
566
|
+
expect(result).toBe("https://example.com/myapp/api");
|
|
567
|
+
});
|
|
568
|
+
test("root with query parameters (should be ignored)", () => {
|
|
569
|
+
const config = {
|
|
570
|
+
root: "http://example.com/myapp?param=value",
|
|
571
|
+
api_prefix: "/api",
|
|
572
|
+
theme: "default",
|
|
573
|
+
version: "1.0.0",
|
|
574
|
+
autoscroll: true
|
|
575
|
+
};
|
|
576
|
+
const result = get_api_url(config);
|
|
577
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
578
|
+
});
|
|
579
|
+
test("root with hash (should be ignored)", () => {
|
|
580
|
+
const config = {
|
|
581
|
+
root: "http://example.com/myapp#section",
|
|
582
|
+
api_prefix: "/api",
|
|
583
|
+
theme: "default",
|
|
584
|
+
version: "1.0.0",
|
|
585
|
+
autoscroll: true
|
|
586
|
+
};
|
|
587
|
+
const result = get_api_url(config);
|
|
588
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
describe("consistency checks", () => {
|
|
592
|
+
test("same result regardless of root trailing slash", () => {
|
|
593
|
+
const baseConfig = {
|
|
594
|
+
api_prefix: "/api",
|
|
595
|
+
theme: "default",
|
|
596
|
+
version: "1.0.0",
|
|
597
|
+
autoscroll: true
|
|
598
|
+
};
|
|
599
|
+
const config1 = {
|
|
600
|
+
...baseConfig,
|
|
601
|
+
root: "http://example.com/myapp"
|
|
602
|
+
};
|
|
603
|
+
const config2 = {
|
|
604
|
+
...baseConfig,
|
|
605
|
+
root: "http://example.com/myapp/"
|
|
606
|
+
};
|
|
607
|
+
expect(get_api_url(config1)).toBe(get_api_url(config2));
|
|
608
|
+
});
|
|
609
|
+
test("same result regardless of api_prefix leading slash", () => {
|
|
610
|
+
const baseConfig = {
|
|
611
|
+
root: "http://example.com/myapp",
|
|
612
|
+
theme: "default",
|
|
613
|
+
version: "1.0.0",
|
|
614
|
+
autoscroll: true
|
|
615
|
+
};
|
|
616
|
+
const config1 = {
|
|
617
|
+
...baseConfig,
|
|
618
|
+
api_prefix: "/api"
|
|
619
|
+
};
|
|
620
|
+
const config2 = {
|
|
621
|
+
...baseConfig,
|
|
622
|
+
api_prefix: "api"
|
|
623
|
+
};
|
|
624
|
+
expect(get_api_url(config1)).toBe(get_api_url(config2));
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
});
|
package/package.json
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/core",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"devDependencies": {
|
|
6
|
-
"@gradio/accordion": "^0.5.
|
|
7
|
-
"@gradio/annotatedimage": "^0.11.4",
|
|
8
|
-
"@gradio/audio": "^0.22.4",
|
|
6
|
+
"@gradio/accordion": "^0.5.33",
|
|
9
7
|
"@gradio/atoms": "^0.22.2",
|
|
8
|
+
"@gradio/annotatedimage": "^0.11.5",
|
|
9
|
+
"@gradio/audio": "^0.23.0",
|
|
10
10
|
"@gradio/box": "^0.2.30",
|
|
11
|
-
"@gradio/button": "^0.6.5",
|
|
12
11
|
"@gradio/browserstate": "^0.3.7",
|
|
13
|
-
"@gradio/
|
|
14
|
-
"@gradio/
|
|
15
|
-
"@gradio/
|
|
12
|
+
"@gradio/button": "^0.6.6",
|
|
13
|
+
"@gradio/chatbot": "^0.29.6",
|
|
14
|
+
"@gradio/checkbox": "^0.6.5",
|
|
15
|
+
"@gradio/checkboxgroup": "^0.10.0",
|
|
16
|
+
"@gradio/code": "^0.17.5",
|
|
16
17
|
"@gradio/client": "^2.1.0",
|
|
17
|
-
"@gradio/code": "^0.17.4",
|
|
18
|
-
"@gradio/colorpicker": "^0.5.7",
|
|
19
18
|
"@gradio/column": "^0.3.2",
|
|
20
|
-
"@gradio/
|
|
21
|
-
"@gradio/
|
|
19
|
+
"@gradio/dataframe": "^0.22.0",
|
|
20
|
+
"@gradio/dataset": "^0.5.6",
|
|
21
|
+
"@gradio/colorpicker": "^0.5.8",
|
|
22
|
+
"@gradio/datetime": "^0.4.5",
|
|
22
23
|
"@gradio/downloadbutton": "^0.4.18",
|
|
23
|
-
"@gradio/
|
|
24
|
-
"@gradio/
|
|
25
|
-
"@gradio/file": "^0.14.
|
|
26
|
-
"@gradio/
|
|
27
|
-
"@gradio/fileexplorer": "^0.6.4",
|
|
28
|
-
"@gradio/gallery": "^0.17.3",
|
|
24
|
+
"@gradio/dropdown": "^0.11.6",
|
|
25
|
+
"@gradio/fallback": "^0.4.36",
|
|
26
|
+
"@gradio/file": "^0.14.5",
|
|
27
|
+
"@gradio/fileexplorer": "^0.6.5",
|
|
29
28
|
"@gradio/form": "^0.3.1",
|
|
29
|
+
"@gradio/gallery": "^0.17.4",
|
|
30
30
|
"@gradio/group": "^0.3.3",
|
|
31
|
-
"@gradio/
|
|
32
|
-
"@gradio/
|
|
31
|
+
"@gradio/highlightedtext": "^0.11.4",
|
|
32
|
+
"@gradio/html": "^0.12.0",
|
|
33
33
|
"@gradio/icons": "^0.15.1",
|
|
34
|
-
"@gradio/
|
|
35
|
-
"@gradio/
|
|
36
|
-
"@gradio/imageslider": "^0.4.
|
|
37
|
-
"@gradio/json": "^0.7.
|
|
38
|
-
"@gradio/
|
|
39
|
-
"@gradio/markdown": "^0.13.
|
|
40
|
-
"@gradio/multimodaltextbox": "^0.11.
|
|
41
|
-
"@gradio/
|
|
42
|
-
"@gradio/nativeplot": "^0.10.
|
|
43
|
-
"@gradio/
|
|
44
|
-
"@gradio/
|
|
45
|
-
"@gradio/plot": "^0.10.
|
|
46
|
-
"@gradio/
|
|
47
|
-
"@gradio/radio": "^0.9.4",
|
|
48
|
-
"@gradio/simpledropdown": "^0.3.35",
|
|
49
|
-
"@gradio/simpleimage": "^0.9.6",
|
|
50
|
-
"@gradio/simpletextbox": "^0.3.37",
|
|
34
|
+
"@gradio/imageeditor": "^0.18.8",
|
|
35
|
+
"@gradio/image": "^0.26.0",
|
|
36
|
+
"@gradio/imageslider": "^0.4.5",
|
|
37
|
+
"@gradio/json": "^0.7.4",
|
|
38
|
+
"@gradio/model3d": "^0.16.6",
|
|
39
|
+
"@gradio/markdown": "^0.13.30",
|
|
40
|
+
"@gradio/multimodaltextbox": "^0.11.8",
|
|
41
|
+
"@gradio/label": "^0.6.5",
|
|
42
|
+
"@gradio/nativeplot": "^0.10.4",
|
|
43
|
+
"@gradio/number": "^0.8.5",
|
|
44
|
+
"@gradio/radio": "^0.10.0",
|
|
45
|
+
"@gradio/plot": "^0.10.6",
|
|
46
|
+
"@gradio/paramviewer": "^0.9.6",
|
|
51
47
|
"@gradio/row": "^0.3.1",
|
|
48
|
+
"@gradio/simpleimage": "^0.9.7",
|
|
49
|
+
"@gradio/simpledropdown": "^0.3.36",
|
|
50
|
+
"@gradio/simpletextbox": "^0.3.38",
|
|
51
|
+
"@gradio/sidebar": "^0.2.5",
|
|
52
52
|
"@gradio/state": "^0.2.3",
|
|
53
|
-
"@gradio/slider": "^0.7.
|
|
54
|
-
"@gradio/
|
|
55
|
-
"@gradio/statustracker": "^0.12.5",
|
|
56
|
-
"@gradio/textbox": "^0.13.5",
|
|
53
|
+
"@gradio/slider": "^0.7.8",
|
|
54
|
+
"@gradio/statustracker": "^0.13.0",
|
|
57
55
|
"@gradio/tabs": "^0.5.8",
|
|
56
|
+
"@gradio/tabitem": "^0.6.6",
|
|
57
|
+
"@gradio/textbox": "^0.13.6",
|
|
58
58
|
"@gradio/theme": "^0.6.1",
|
|
59
59
|
"@gradio/timer": "^0.4.9",
|
|
60
60
|
"@gradio/upload": "^0.17.7",
|
|
61
|
-
"@gradio/utils": "^0.12.
|
|
61
|
+
"@gradio/utils": "^0.12.1",
|
|
62
62
|
"@gradio/uploadbutton": "^0.9.18",
|
|
63
|
-
"@gradio/
|
|
64
|
-
"@gradio/
|
|
63
|
+
"@gradio/vibeeditor": "^0.3.7",
|
|
64
|
+
"@gradio/video": "^0.20.5"
|
|
65
65
|
},
|
|
66
66
|
"msw": {
|
|
67
67
|
"workerDirectory": "public"
|
package/src/i18n.test.ts
CHANGED
|
@@ -8,8 +8,31 @@ import {
|
|
|
8
8
|
afterEach
|
|
9
9
|
} from "vitest";
|
|
10
10
|
import { Lang, process_langs } from "./i18n";
|
|
11
|
-
|
|
11
|
+
// wikidata-lang/indexes/by_any_code uses Node.js createRequire internally.
|
|
12
|
+
// Import the raw JSON data and build the index directly for browser compatibility.
|
|
13
|
+
import languages from "wikidata-lang/data/languages.json";
|
|
12
14
|
import BCP47 from "./lang/BCP47_codes";
|
|
15
|
+
|
|
16
|
+
const languagesByAnyCode: Record<string, any[]> = {};
|
|
17
|
+
for (const langData of languages) {
|
|
18
|
+
for (const codeName of [
|
|
19
|
+
"wmCode",
|
|
20
|
+
"iso6391",
|
|
21
|
+
"iso6392",
|
|
22
|
+
"iso6393",
|
|
23
|
+
"iso6396"
|
|
24
|
+
]) {
|
|
25
|
+
const codes = (langData as Record<string, any>)[codeName];
|
|
26
|
+
if (!codes) continue;
|
|
27
|
+
for (const code of codes) {
|
|
28
|
+
if (languagesByAnyCode[code] == null) {
|
|
29
|
+
languagesByAnyCode[code] = [langData];
|
|
30
|
+
} else if (!languagesByAnyCode[code].includes(langData)) {
|
|
31
|
+
languagesByAnyCode[code].push(langData);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
13
36
|
import {
|
|
14
37
|
get_initial_locale,
|
|
15
38
|
load_translations,
|
|
@@ -25,7 +48,10 @@ vi.mock("svelte-i18n", () => ({
|
|
|
25
48
|
locale: { set: vi.fn() },
|
|
26
49
|
_: vi.fn((key) => `translated_${key}`),
|
|
27
50
|
addMessages: vi.fn(),
|
|
28
|
-
init: vi.fn().mockResolvedValue(undefined)
|
|
51
|
+
init: vi.fn().mockResolvedValue(undefined),
|
|
52
|
+
getLocaleFromNavigator: vi.fn(() => "en"),
|
|
53
|
+
register: vi.fn(),
|
|
54
|
+
waitLocale: vi.fn().mockResolvedValue(undefined)
|
|
29
55
|
}));
|
|
30
56
|
|
|
31
57
|
const mock_translations: Record<string, string> = {
|
package/src/init.svelte.ts
CHANGED
|
@@ -40,6 +40,20 @@ const type_map = {
|
|
|
40
40
|
walkthrough: "tabs",
|
|
41
41
|
walkthroughstep: "tabitem"
|
|
42
42
|
};
|
|
43
|
+
|
|
44
|
+
export function get_api_url(config: Omit<AppConfig, "api_url">): string {
|
|
45
|
+
// Handle api_prefix correctly when app is mounted at a subpath.
|
|
46
|
+
// config.root may not include a trailing slash, so we normalize its pathname
|
|
47
|
+
// before appending api_prefix to ensure correct URL construction.
|
|
48
|
+
const rootUrl = new URL(config.root);
|
|
49
|
+
const rootPath = rootUrl.pathname.endsWith("/")
|
|
50
|
+
? rootUrl.pathname
|
|
51
|
+
: rootUrl.pathname + "/";
|
|
52
|
+
const apiPrefix = config.api_prefix.startsWith("/")
|
|
53
|
+
? config.api_prefix
|
|
54
|
+
: "/" + config.api_prefix;
|
|
55
|
+
return new URL(rootPath.slice(0, -1) + apiPrefix, rootUrl.origin).toString();
|
|
56
|
+
}
|
|
43
57
|
export class AppTree {
|
|
44
58
|
/** the raw component structure received from the backend */
|
|
45
59
|
#component_payload: ComponentMeta[];
|
|
@@ -91,9 +105,10 @@ export class AppTree {
|
|
|
91
105
|
this.ready_resolve = resolve;
|
|
92
106
|
});
|
|
93
107
|
this.reactive_formatter = reactive_formatter;
|
|
108
|
+
const api_url = get_api_url(config);
|
|
94
109
|
this.#config = {
|
|
95
110
|
...config,
|
|
96
|
-
api_url
|
|
111
|
+
api_url
|
|
97
112
|
};
|
|
98
113
|
this.#component_payload = components;
|
|
99
114
|
this.#layout_payload = layout;
|
|
@@ -144,9 +159,10 @@ export class AppTree {
|
|
|
144
159
|
) {
|
|
145
160
|
this.#layout_payload = layout;
|
|
146
161
|
this.#component_payload = components;
|
|
162
|
+
const api_url = get_api_url(config);
|
|
147
163
|
this.#config = {
|
|
148
164
|
...config,
|
|
149
|
-
api_url
|
|
165
|
+
api_url
|
|
150
166
|
};
|
|
151
167
|
this.#dependency_payload = dependencies;
|
|
152
168
|
|
|
@@ -671,8 +687,9 @@ function gather_props(
|
|
|
671
687
|
|
|
672
688
|
_shared_props.load_component = (
|
|
673
689
|
name: string,
|
|
674
|
-
variant: "base" | "component" | "example"
|
|
675
|
-
|
|
690
|
+
variant: "base" | "component" | "example",
|
|
691
|
+
component_class_id?: string
|
|
692
|
+
) => get_component(name, component_class_id || "", api_url, variant);
|
|
676
693
|
|
|
677
694
|
_shared_props.visible =
|
|
678
695
|
_shared_props.visible === undefined ? true : _shared_props.visible;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, test, expect, vi } from "vitest";
|
|
2
2
|
import { spy } from "tinyspy";
|
|
3
|
-
import {
|
|
3
|
+
import { setupWorker } from "msw/browser";
|
|
4
4
|
import { http, HttpResponse } from "msw";
|
|
5
5
|
import type { client_return } from "@gradio/client";
|
|
6
6
|
import { Dependency, TargetMap } from "./types";
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
process_server_fn,
|
|
12
12
|
get_component
|
|
13
13
|
} from "./_init";
|
|
14
|
+
import { get_api_url } from "./init.svelte";
|
|
15
|
+
import type { AppConfig } from "./types";
|
|
16
|
+
import { commands } from "@vitest/browser/context";
|
|
14
17
|
|
|
15
18
|
describe("process_frontend_fn", () => {
|
|
16
19
|
test("empty source code returns null", () => {
|
|
@@ -461,6 +464,8 @@ describe("get_component", () => {
|
|
|
461
464
|
value: "hi",
|
|
462
465
|
interactive: false
|
|
463
466
|
},
|
|
467
|
+
key: "test-component-one",
|
|
468
|
+
|
|
464
469
|
has_modes: false,
|
|
465
470
|
instance: {} as any,
|
|
466
471
|
component: {} as any
|
|
@@ -512,13 +517,307 @@ describe("get_component", () => {
|
|
|
512
517
|
}
|
|
513
518
|
);
|
|
514
519
|
|
|
515
|
-
const
|
|
516
|
-
|
|
520
|
+
const worker = setupWorker(...handlers);
|
|
521
|
+
worker.start();
|
|
517
522
|
|
|
518
523
|
await get_component("test-random", id, api_url, []).component;
|
|
519
524
|
|
|
520
525
|
expect(mock).toHaveBeenCalled();
|
|
521
526
|
|
|
522
|
-
|
|
527
|
+
worker.stop();
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
describe("get_api_url", () => {
|
|
532
|
+
describe("root URL with trailing slash", () => {
|
|
533
|
+
test("root with trailing slash, api_prefix with leading slash", () => {
|
|
534
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
535
|
+
root: "http://example.com/myapp/",
|
|
536
|
+
api_prefix: "/api",
|
|
537
|
+
theme: "default",
|
|
538
|
+
version: "1.0.0",
|
|
539
|
+
autoscroll: true
|
|
540
|
+
};
|
|
541
|
+
const result = get_api_url(config);
|
|
542
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test("root with trailing slash, api_prefix without leading slash", () => {
|
|
546
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
547
|
+
root: "http://example.com/myapp/",
|
|
548
|
+
api_prefix: "api",
|
|
549
|
+
theme: "default",
|
|
550
|
+
version: "1.0.0",
|
|
551
|
+
autoscroll: true
|
|
552
|
+
};
|
|
553
|
+
const result = get_api_url(config);
|
|
554
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test("root at domain root with trailing slash", () => {
|
|
558
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
559
|
+
root: "http://example.com/",
|
|
560
|
+
api_prefix: "/api",
|
|
561
|
+
theme: "default",
|
|
562
|
+
version: "1.0.0",
|
|
563
|
+
autoscroll: true
|
|
564
|
+
};
|
|
565
|
+
const result = get_api_url(config);
|
|
566
|
+
expect(result).toBe("http://example.com/api");
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
describe("root URL without trailing slash", () => {
|
|
571
|
+
test("root without trailing slash, api_prefix with leading slash", () => {
|
|
572
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
573
|
+
root: "http://example.com/myapp",
|
|
574
|
+
api_prefix: "/api",
|
|
575
|
+
theme: "default",
|
|
576
|
+
version: "1.0.0",
|
|
577
|
+
autoscroll: true
|
|
578
|
+
};
|
|
579
|
+
const result = get_api_url(config);
|
|
580
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
test("root without trailing slash, api_prefix without leading slash", () => {
|
|
584
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
585
|
+
root: "http://example.com/myapp",
|
|
586
|
+
api_prefix: "api",
|
|
587
|
+
theme: "default",
|
|
588
|
+
version: "1.0.0",
|
|
589
|
+
autoscroll: true
|
|
590
|
+
};
|
|
591
|
+
const result = get_api_url(config);
|
|
592
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
test("root at domain root without trailing slash", () => {
|
|
596
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
597
|
+
root: "http://example.com",
|
|
598
|
+
api_prefix: "/api",
|
|
599
|
+
theme: "default",
|
|
600
|
+
version: "1.0.0",
|
|
601
|
+
autoscroll: true
|
|
602
|
+
};
|
|
603
|
+
const result = get_api_url(config);
|
|
604
|
+
expect(result).toBe("http://example.com/api");
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
describe("different root path combinations", () => {
|
|
609
|
+
test("root path is just '/'", () => {
|
|
610
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
611
|
+
root: "http://example.com/",
|
|
612
|
+
api_prefix: "/api",
|
|
613
|
+
theme: "default",
|
|
614
|
+
version: "1.0.0",
|
|
615
|
+
autoscroll: true
|
|
616
|
+
};
|
|
617
|
+
const result = get_api_url(config);
|
|
618
|
+
expect(result).toBe("http://example.com/api");
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
test("root path is '/' without trailing slash", () => {
|
|
622
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
623
|
+
root: "http://example.com",
|
|
624
|
+
api_prefix: "/api",
|
|
625
|
+
theme: "default",
|
|
626
|
+
version: "1.0.0",
|
|
627
|
+
autoscroll: true
|
|
628
|
+
};
|
|
629
|
+
const result = get_api_url(config);
|
|
630
|
+
expect(result).toBe("http://example.com/api");
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test("root path is '/myapp'", () => {
|
|
634
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
635
|
+
root: "http://example.com/myapp",
|
|
636
|
+
api_prefix: "/api",
|
|
637
|
+
theme: "default",
|
|
638
|
+
version: "1.0.0",
|
|
639
|
+
autoscroll: true
|
|
640
|
+
};
|
|
641
|
+
const result = get_api_url(config);
|
|
642
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
test("root path is '/myapp/'", () => {
|
|
646
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
647
|
+
root: "http://example.com/myapp/",
|
|
648
|
+
api_prefix: "/api",
|
|
649
|
+
theme: "default",
|
|
650
|
+
version: "1.0.0",
|
|
651
|
+
autoscroll: true
|
|
652
|
+
};
|
|
653
|
+
const result = get_api_url(config);
|
|
654
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("root path is '/deep/nested/path'", () => {
|
|
658
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
659
|
+
root: "http://example.com/deep/nested/path",
|
|
660
|
+
api_prefix: "/api",
|
|
661
|
+
theme: "default",
|
|
662
|
+
version: "1.0.0",
|
|
663
|
+
autoscroll: true
|
|
664
|
+
};
|
|
665
|
+
const result = get_api_url(config);
|
|
666
|
+
expect(result).toBe("http://example.com/deep/nested/path/api");
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
test("root path is '/deep/nested/path/'", () => {
|
|
670
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
671
|
+
root: "http://example.com/deep/nested/path/",
|
|
672
|
+
api_prefix: "/api",
|
|
673
|
+
theme: "default",
|
|
674
|
+
version: "1.0.0",
|
|
675
|
+
autoscroll: true
|
|
676
|
+
};
|
|
677
|
+
const result = get_api_url(config);
|
|
678
|
+
expect(result).toBe("http://example.com/deep/nested/path/api");
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
describe("different api_prefix formats", () => {
|
|
683
|
+
test("api_prefix with leading slash", () => {
|
|
684
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
685
|
+
root: "http://example.com/myapp",
|
|
686
|
+
api_prefix: "/api",
|
|
687
|
+
theme: "default",
|
|
688
|
+
version: "1.0.0",
|
|
689
|
+
autoscroll: true
|
|
690
|
+
};
|
|
691
|
+
const result = get_api_url(config);
|
|
692
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test("api_prefix without leading slash", () => {
|
|
696
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
697
|
+
root: "http://example.com/myapp",
|
|
698
|
+
api_prefix: "api",
|
|
699
|
+
theme: "default",
|
|
700
|
+
version: "1.0.0",
|
|
701
|
+
autoscroll: true
|
|
702
|
+
};
|
|
703
|
+
const result = get_api_url(config);
|
|
704
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("api_prefix with nested path and leading slash", () => {
|
|
708
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
709
|
+
root: "http://example.com/myapp",
|
|
710
|
+
api_prefix: "/api/v1",
|
|
711
|
+
theme: "default",
|
|
712
|
+
version: "1.0.0",
|
|
713
|
+
autoscroll: true
|
|
714
|
+
};
|
|
715
|
+
const result = get_api_url(config);
|
|
716
|
+
expect(result).toBe("http://example.com/myapp/api/v1");
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
test("api_prefix with nested path without leading slash", () => {
|
|
720
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
721
|
+
root: "http://example.com/myapp",
|
|
722
|
+
api_prefix: "api/v1",
|
|
723
|
+
theme: "default",
|
|
724
|
+
version: "1.0.0",
|
|
725
|
+
autoscroll: true
|
|
726
|
+
};
|
|
727
|
+
const result = get_api_url(config);
|
|
728
|
+
expect(result).toBe("http://example.com/myapp/api/v1");
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
describe("edge cases", () => {
|
|
733
|
+
test("root with port number", () => {
|
|
734
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
735
|
+
root: "http://example.com:8080/myapp",
|
|
736
|
+
api_prefix: "/api",
|
|
737
|
+
theme: "default",
|
|
738
|
+
version: "1.0.0",
|
|
739
|
+
autoscroll: true
|
|
740
|
+
};
|
|
741
|
+
const result = get_api_url(config);
|
|
742
|
+
expect(result).toBe("http://example.com:8080/myapp/api");
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
test("root with HTTPS", () => {
|
|
746
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
747
|
+
root: "https://example.com/myapp",
|
|
748
|
+
api_prefix: "/api",
|
|
749
|
+
theme: "default",
|
|
750
|
+
version: "1.0.0",
|
|
751
|
+
autoscroll: true
|
|
752
|
+
};
|
|
753
|
+
const result = get_api_url(config);
|
|
754
|
+
expect(result).toBe("https://example.com/myapp/api");
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
test("root with query parameters (should be ignored)", () => {
|
|
758
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
759
|
+
root: "http://example.com/myapp?param=value",
|
|
760
|
+
api_prefix: "/api",
|
|
761
|
+
theme: "default",
|
|
762
|
+
version: "1.0.0",
|
|
763
|
+
autoscroll: true
|
|
764
|
+
};
|
|
765
|
+
const result = get_api_url(config);
|
|
766
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
test("root with hash (should be ignored)", () => {
|
|
770
|
+
const config: Omit<AppConfig, "api_url"> = {
|
|
771
|
+
root: "http://example.com/myapp#section",
|
|
772
|
+
api_prefix: "/api",
|
|
773
|
+
theme: "default",
|
|
774
|
+
version: "1.0.0",
|
|
775
|
+
autoscroll: true
|
|
776
|
+
};
|
|
777
|
+
const result = get_api_url(config);
|
|
778
|
+
expect(result).toBe("http://example.com/myapp/api");
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
describe("consistency checks", () => {
|
|
783
|
+
test("same result regardless of root trailing slash", () => {
|
|
784
|
+
const baseConfig = {
|
|
785
|
+
api_prefix: "/api",
|
|
786
|
+
theme: "default",
|
|
787
|
+
version: "1.0.0",
|
|
788
|
+
autoscroll: true
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
const config1: Omit<AppConfig, "api_url"> = {
|
|
792
|
+
...baseConfig,
|
|
793
|
+
root: "http://example.com/myapp"
|
|
794
|
+
};
|
|
795
|
+
const config2: Omit<AppConfig, "api_url"> = {
|
|
796
|
+
...baseConfig,
|
|
797
|
+
root: "http://example.com/myapp/"
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
expect(get_api_url(config1)).toBe(get_api_url(config2));
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
test("same result regardless of api_prefix leading slash", () => {
|
|
804
|
+
const baseConfig = {
|
|
805
|
+
root: "http://example.com/myapp",
|
|
806
|
+
theme: "default",
|
|
807
|
+
version: "1.0.0",
|
|
808
|
+
autoscroll: true
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
const config1: Omit<AppConfig, "api_url"> = {
|
|
812
|
+
...baseConfig,
|
|
813
|
+
api_prefix: "/api"
|
|
814
|
+
};
|
|
815
|
+
const config2: Omit<AppConfig, "api_url"> = {
|
|
816
|
+
...baseConfig,
|
|
817
|
+
api_prefix: "api"
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
expect(get_api_url(config1)).toBe(get_api_url(config2));
|
|
821
|
+
});
|
|
523
822
|
});
|
|
524
823
|
});
|