@gradio/client 2.3.1 → 2.5.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 +21 -0
- package/README.md +4 -2
- package/dist/__vite-browser-external-Cgmn0awE.js +7 -0
- package/dist/browser.js +1949 -1963
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/helpers/api_info.d.ts.map +1 -1
- package/dist/helpers/data.d.ts.map +1 -1
- package/dist/helpers/init_helpers.d.ts +11 -2
- package/dist/helpers/init_helpers.d.ts.map +1 -1
- package/dist/index.cjs +2584 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2471 -2466
- package/dist/index.min.js +1962 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/duplicate.d.ts.map +1 -1
- package/dist/utils/predict.d.ts.map +1 -1
- package/dist/utils/run_history.d.ts +64 -0
- package/dist/utils/run_history.d.ts.map +1 -0
- package/dist/utils/stream.d.ts.map +1 -1
- package/dist/utils/submit.d.ts.map +1 -1
- package/package.json +11 -4
- package/scripts/verify_dist.mjs +46 -0
- package/src/client.ts +54 -5
- package/src/constants.ts +10 -0
- package/src/helpers/api_info.ts +46 -19
- package/src/helpers/data.ts +8 -3
- package/src/helpers/init_helpers.ts +54 -7
- package/src/index.ts +12 -0
- package/src/test/api_info.test.ts +77 -12
- package/src/test/data.test.ts +26 -3
- package/src/test/init.test.ts +21 -3
- package/src/test/init_helpers.test.ts +67 -1
- package/src/test/refresh.test.ts +31 -0
- package/src/test/run_history.test.ts +345 -0
- package/src/test/submit.test.ts +1 -3
- package/src/types.ts +29 -0
- package/src/utils/duplicate.ts +2 -0
- package/src/utils/predict.ts +14 -1
- package/src/utils/run_history.ts +520 -0
- package/src/utils/stream.ts +17 -4
- package/src/utils/submit.ts +105 -12
- package/vite.config.ts +20 -4
- package/dist/__vite-browser-external-DYxpcVy9.js +0 -4
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { Config } from "../types";
|
|
1
|
+
import type { ClientOptions, Config } from "../types";
|
|
2
2
|
import {
|
|
3
3
|
CONFIG_ERROR_MSG,
|
|
4
4
|
CONFIG_URL,
|
|
5
5
|
INVALID_CREDENTIALS_MSG,
|
|
6
6
|
LOGIN_URL,
|
|
7
7
|
MISSING_CREDENTIALS_MSG,
|
|
8
|
+
PRIVATE_SPACE_MSG,
|
|
8
9
|
SPACE_METADATA_ERROR_MSG,
|
|
9
10
|
UNAUTHORIZED_MSG
|
|
10
11
|
} from "../constants";
|
|
@@ -53,6 +54,22 @@ export async function get_jwt(
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
* The `hf_token` option was renamed to `token`, but a lot of existing code
|
|
59
|
+
* (and the Python client) still uses `hf_token`. Accept it as an alias so
|
|
60
|
+
* that authenticated requests are not silently sent without credentials,
|
|
61
|
+
* which previously surfaced as "Could not resolve app config" errors when
|
|
62
|
+
* connecting to private Spaces.
|
|
63
|
+
*/
|
|
64
|
+
export function normalise_token_option(options: ClientOptions): void {
|
|
65
|
+
if (options.hf_token && !options.token) {
|
|
66
|
+
options.token = options.hf_token;
|
|
67
|
+
console.warn(
|
|
68
|
+
"The `hf_token` option has been renamed to `token`. Support for `hf_token` will be removed in a future version of @gradio/client."
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
export function map_names_to_ids(
|
|
57
74
|
fns: Config["dependencies"]
|
|
58
75
|
): Record<string, number> {
|
|
@@ -64,9 +81,25 @@ export function map_names_to_ids(
|
|
|
64
81
|
return apis;
|
|
65
82
|
}
|
|
66
83
|
|
|
84
|
+
export function resolve_config_root(
|
|
85
|
+
root: string,
|
|
86
|
+
current_location: string
|
|
87
|
+
): string {
|
|
88
|
+
const root_url = new URL(root, current_location);
|
|
89
|
+
const current_url = new URL(current_location);
|
|
90
|
+
if (root_url.hostname !== current_url.hostname) {
|
|
91
|
+
return root;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
root_url.protocol = current_url.protocol;
|
|
95
|
+
root_url.host = current_url.host;
|
|
96
|
+
return root_url.toString().replace(/\/$/, "");
|
|
97
|
+
}
|
|
98
|
+
|
|
67
99
|
export async function resolve_config(
|
|
68
100
|
this: Client,
|
|
69
|
-
endpoint: string
|
|
101
|
+
endpoint: string,
|
|
102
|
+
strip_current_page = true
|
|
70
103
|
): Promise<Config | undefined> {
|
|
71
104
|
const headers: Record<string, string> = this.options.token
|
|
72
105
|
? { Authorization: `Bearer ${this.options.token}` }
|
|
@@ -77,7 +110,7 @@ export async function resolve_config(
|
|
|
77
110
|
window.gradio_config &&
|
|
78
111
|
location.origin !== "http://localhost:9876"
|
|
79
112
|
) {
|
|
80
|
-
if (window.gradio_config.current_page) {
|
|
113
|
+
if (strip_current_page && window.gradio_config.current_page) {
|
|
81
114
|
endpoint = endpoint.substring(0, endpoint.lastIndexOf("/"));
|
|
82
115
|
}
|
|
83
116
|
if (
|
|
@@ -102,8 +135,13 @@ export async function resolve_config(
|
|
|
102
135
|
current_page: window.gradio_config.current_page
|
|
103
136
|
};
|
|
104
137
|
}
|
|
105
|
-
//
|
|
106
|
-
|
|
138
|
+
// The page was rendered by this Gradio server, so a same-host root may
|
|
139
|
+
// contain an internal protocol or port supplied by a reverse proxy. Keep
|
|
140
|
+
// the configured path, but use the browser-visible origin for requests
|
|
141
|
+
// made by the client itself (queue, upload, reset, etc.).
|
|
142
|
+
const config = { ...window.gradio_config } as unknown as Config;
|
|
143
|
+
config.root = resolve_config_root(config.root, location.href);
|
|
144
|
+
return config;
|
|
107
145
|
} else if (endpoint) {
|
|
108
146
|
let config_url = join_urls(
|
|
109
147
|
endpoint,
|
|
@@ -132,7 +170,14 @@ async function handleConfigResponse(
|
|
|
132
170
|
authorized: boolean
|
|
133
171
|
): Promise<Config> {
|
|
134
172
|
if (response?.status === 401 && !authorized) {
|
|
135
|
-
|
|
173
|
+
let error_data: any = null;
|
|
174
|
+
try {
|
|
175
|
+
error_data = await response.json();
|
|
176
|
+
} catch (e) {
|
|
177
|
+
// Unauthenticated requests to private Spaces receive a non-JSON 401
|
|
178
|
+
// page from the Hugging Face proxy rather than a Gradio auth payload.
|
|
179
|
+
throw new Error(PRIVATE_SPACE_MSG);
|
|
180
|
+
}
|
|
136
181
|
const auth_message = error_data?.detail?.auth_message;
|
|
137
182
|
throw new Error(auth_message || MISSING_CREDENTIALS_MSG);
|
|
138
183
|
} else if (response?.status === 401 && authorized) {
|
|
@@ -151,7 +196,9 @@ async function handleConfigResponse(
|
|
|
151
196
|
throw new Error(UNAUTHORIZED_MSG);
|
|
152
197
|
}
|
|
153
198
|
|
|
154
|
-
throw new Error(
|
|
199
|
+
throw new Error(
|
|
200
|
+
`${CONFIG_ERROR_MSG}(received status ${response?.status} when fetching the app config)`
|
|
201
|
+
);
|
|
155
202
|
}
|
|
156
203
|
|
|
157
204
|
export async function resolve_cookies(this: Client): Promise<void> {
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,18 @@ export { submit } from "./utils/submit";
|
|
|
5
5
|
export { upload_files } from "./utils/upload_files";
|
|
6
6
|
export { FileData, upload, prepare_files } from "./upload";
|
|
7
7
|
export { handle_file } from "./helpers/data";
|
|
8
|
+
export {
|
|
9
|
+
clear_run_history,
|
|
10
|
+
consume_run_history_replay,
|
|
11
|
+
delete_run_history,
|
|
12
|
+
on_run_history_change,
|
|
13
|
+
read_run_history,
|
|
14
|
+
run_history_url,
|
|
15
|
+
stage_run_history_replay,
|
|
16
|
+
type RunHistoryScope,
|
|
17
|
+
type StoredRunComponent,
|
|
18
|
+
type StoredRun
|
|
19
|
+
} from "./utils/run_history";
|
|
8
20
|
|
|
9
21
|
export type {
|
|
10
22
|
SpaceStatus,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
INVALID_URL_MSG,
|
|
3
3
|
QUEUE_FULL_MSG,
|
|
4
|
-
|
|
4
|
+
SPACE_NOT_FOUND_MSG
|
|
5
5
|
} from "../constants";
|
|
6
6
|
import { beforeAll, afterEach, afterAll, it, expect, describe } from "vitest";
|
|
7
7
|
import {
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
get_type,
|
|
11
11
|
process_endpoint,
|
|
12
12
|
join_urls,
|
|
13
|
-
map_data_to_params
|
|
13
|
+
map_data_to_params,
|
|
14
|
+
transform_api_info
|
|
14
15
|
} from "../helpers/api_info";
|
|
15
16
|
import { initialise_server } from "./server";
|
|
16
17
|
import { transformed_api_info } from "./test_data";
|
|
@@ -471,19 +472,13 @@ describe("process_endpoint", () => {
|
|
|
471
472
|
expect(result).toEqual(expected);
|
|
472
473
|
});
|
|
473
474
|
|
|
474
|
-
it("should throw
|
|
475
|
+
it("should throw a clear error when the space does not exist or is private", async () => {
|
|
475
476
|
const app_reference = "hmb/bye_world";
|
|
476
477
|
const token = "hf_token";
|
|
477
478
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (error instanceof Error) {
|
|
482
|
-
expect(error.message).toEqual(SPACE_METADATA_ERROR_MSG);
|
|
483
|
-
} else {
|
|
484
|
-
expect.fail("Error should not be unknown.");
|
|
485
|
-
}
|
|
486
|
-
}
|
|
479
|
+
await expect(process_endpoint(app_reference, token)).rejects.toThrow(
|
|
480
|
+
SPACE_NOT_FOUND_MSG(app_reference, 404)
|
|
481
|
+
);
|
|
487
482
|
});
|
|
488
483
|
|
|
489
484
|
it("should return the correct data when app_reference is a valid space domain", async () => {
|
|
@@ -699,3 +694,73 @@ describe("map_data_params", () => {
|
|
|
699
694
|
);
|
|
700
695
|
});
|
|
701
696
|
});
|
|
697
|
+
|
|
698
|
+
describe("transform_api_info", () => {
|
|
699
|
+
it("defaults parameters and returns to empty arrays when an endpoint entry is malformed", () => {
|
|
700
|
+
const api_info = {
|
|
701
|
+
named_endpoints: {
|
|
702
|
+
// missing `parameters` and `returns`, as returned by some legacy
|
|
703
|
+
// or misbehaving apps (see issue #10945)
|
|
704
|
+
"/predict": {}
|
|
705
|
+
},
|
|
706
|
+
unnamed_endpoints: {}
|
|
707
|
+
} as any;
|
|
708
|
+
const config = {
|
|
709
|
+
dependencies: [
|
|
710
|
+
{
|
|
711
|
+
id: 0,
|
|
712
|
+
api_name: "predict",
|
|
713
|
+
inputs: [1],
|
|
714
|
+
outputs: [2],
|
|
715
|
+
types: { generator: false, cancel: false }
|
|
716
|
+
}
|
|
717
|
+
],
|
|
718
|
+
components: [
|
|
719
|
+
{ id: 1, type: "textbox", props: {} },
|
|
720
|
+
{ id: 2, type: "textbox", props: {} }
|
|
721
|
+
]
|
|
722
|
+
} as any;
|
|
723
|
+
|
|
724
|
+
const result = transform_api_info(api_info, config, { predict: 0 });
|
|
725
|
+
|
|
726
|
+
expect(result.named_endpoints["/predict"].parameters).toEqual([]);
|
|
727
|
+
expect(result.named_endpoints["/predict"].returns).toEqual([]);
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
it("keeps oauth_token, which submit() needs to decide where a token may be sent", () => {
|
|
731
|
+
const api_info = {
|
|
732
|
+
named_endpoints: {
|
|
733
|
+
"/report": { parameters: [], returns: [], oauth_token: "optional" },
|
|
734
|
+
"/calculator": { parameters: [], returns: [] }
|
|
735
|
+
},
|
|
736
|
+
unnamed_endpoints: {}
|
|
737
|
+
} as any;
|
|
738
|
+
const config = {
|
|
739
|
+
dependencies: [
|
|
740
|
+
{
|
|
741
|
+
id: 0,
|
|
742
|
+
api_name: "report",
|
|
743
|
+
inputs: [],
|
|
744
|
+
outputs: [],
|
|
745
|
+
types: { generator: false, cancel: false }
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
id: 1,
|
|
749
|
+
api_name: "calculator",
|
|
750
|
+
inputs: [],
|
|
751
|
+
outputs: [],
|
|
752
|
+
types: { generator: false, cancel: false }
|
|
753
|
+
}
|
|
754
|
+
],
|
|
755
|
+
components: []
|
|
756
|
+
} as any;
|
|
757
|
+
|
|
758
|
+
const result = transform_api_info(api_info, config, {
|
|
759
|
+
report: 0,
|
|
760
|
+
calculator: 1
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
expect(result.named_endpoints["/report"].oauth_token).toBe("optional");
|
|
764
|
+
expect(result.named_endpoints["/calculator"].oauth_token).toBeUndefined();
|
|
765
|
+
});
|
|
766
|
+
});
|
package/src/test/data.test.ts
CHANGED
|
@@ -97,6 +97,27 @@ describe("walk_and_store_blobs", () => {
|
|
|
97
97
|
expect(parts[0].path).toEqual(["0"]);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
+
it("should preserve File instances (and their filenames)", async () => {
|
|
101
|
+
const file = new File(["test data"], "report.txt", {
|
|
102
|
+
type: "text/plain"
|
|
103
|
+
});
|
|
104
|
+
const parts = await walk_and_store_blobs([file]);
|
|
105
|
+
|
|
106
|
+
expect(parts).toHaveLength(1);
|
|
107
|
+
expect(parts[0].blob).toBe(file);
|
|
108
|
+
expect((parts[0].blob as File).name).toBe("report.txt");
|
|
109
|
+
expect((parts[0].blob as File).type).toBe("text/plain");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should preserve the MIME type of Blob instances", async () => {
|
|
113
|
+
const blob = new Blob(["test data"], { type: "image/png" });
|
|
114
|
+
const parts = await walk_and_store_blobs([blob]);
|
|
115
|
+
|
|
116
|
+
expect(parts).toHaveLength(1);
|
|
117
|
+
expect(parts[0].blob).toBe(blob);
|
|
118
|
+
expect(parts[0].blob && parts[0].blob.type).toBe("image/png");
|
|
119
|
+
});
|
|
120
|
+
|
|
100
121
|
it("should handle deep structures", async () => {
|
|
101
122
|
const image = new Blob([]);
|
|
102
123
|
const parts = await walk_and_store_blobs({ a: { b: { data: { image } } } });
|
|
@@ -371,11 +392,13 @@ describe("handle_file", () => {
|
|
|
371
392
|
);
|
|
372
393
|
|
|
373
394
|
it.skipIf(IS_NODE)(
|
|
374
|
-
"should handle a File object and return it
|
|
395
|
+
"should handle a File object and return it unchanged, preserving its name and type",
|
|
375
396
|
() => {
|
|
376
397
|
const file = new File(["test image"], "test.png", { type: "image/png" });
|
|
377
|
-
const result = handle_file(file) as
|
|
378
|
-
expect(result).
|
|
398
|
+
const result = handle_file(file) as File;
|
|
399
|
+
expect(result).toBe(file);
|
|
400
|
+
expect(result.name).toBe("test.png");
|
|
401
|
+
expect(result.type).toBe("image/png");
|
|
379
402
|
}
|
|
380
403
|
);
|
|
381
404
|
|
package/src/test/init.test.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
response_api_info
|
|
16
16
|
} from "./test_data";
|
|
17
17
|
import { initialise_server } from "./server";
|
|
18
|
-
import {
|
|
18
|
+
import { SPACE_NOT_FOUND_MSG } from "../constants";
|
|
19
19
|
|
|
20
20
|
const app_reference = "hmb/hello_world";
|
|
21
21
|
const broken_app_reference = "hmb/bye_world";
|
|
@@ -87,12 +87,28 @@ describe("Client class", () => {
|
|
|
87
87
|
});
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
test("connecting successfully to a private running app with the deprecated hf_token option", async () => {
|
|
91
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
92
|
+
const app = await Client.connect("hmb/secret_world", {
|
|
93
|
+
hf_token: "hf_123"
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(app.config).toEqual({
|
|
97
|
+
...config_response,
|
|
98
|
+
root: "https://hmb-secret-world.hf.space"
|
|
99
|
+
});
|
|
100
|
+
expect(warn).toHaveBeenCalledWith(
|
|
101
|
+
expect.stringContaining("`hf_token` option has been renamed")
|
|
102
|
+
);
|
|
103
|
+
warn.mockRestore();
|
|
104
|
+
});
|
|
105
|
+
|
|
90
106
|
test("unsuccessfully attempting to connect to a private running app", async () => {
|
|
91
107
|
await expect(
|
|
92
108
|
Client.connect("hmb/secret_world", {
|
|
93
109
|
token: "hf_bad_token"
|
|
94
110
|
})
|
|
95
|
-
).rejects.toThrowError(
|
|
111
|
+
).rejects.toThrowError(SPACE_NOT_FOUND_MSG("hmb/secret_world", 401));
|
|
96
112
|
});
|
|
97
113
|
|
|
98
114
|
test("viewing the api info of a running app", async () => {
|
|
@@ -139,7 +155,9 @@ describe("Client class", () => {
|
|
|
139
155
|
test("creating a duplicate of a broken app", async () => {
|
|
140
156
|
const duplicate = Client.duplicate(broken_app_reference);
|
|
141
157
|
|
|
142
|
-
await expect(duplicate).rejects.toThrow(
|
|
158
|
+
await expect(duplicate).rejects.toThrow(
|
|
159
|
+
SPACE_NOT_FOUND_MSG(broken_app_reference, 404)
|
|
160
|
+
);
|
|
143
161
|
});
|
|
144
162
|
});
|
|
145
163
|
|
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
get_jwt,
|
|
4
4
|
determine_protocol,
|
|
5
5
|
parse_and_set_cookies,
|
|
6
|
-
resolve_config
|
|
6
|
+
resolve_config,
|
|
7
|
+
resolve_config_root
|
|
7
8
|
} from "../helpers/init_helpers";
|
|
8
9
|
import { initialise_server } from "./server";
|
|
9
10
|
import { beforeAll, afterEach, afterAll, it, expect, describe } from "vitest";
|
|
@@ -21,6 +22,24 @@ afterEach(() => server.resetHandlers());
|
|
|
21
22
|
afterAll(() => server.stop());
|
|
22
23
|
|
|
23
24
|
describe("resolve_config", () => {
|
|
25
|
+
it("uses the public protocol and port for a same-host proxy root", () => {
|
|
26
|
+
expect(
|
|
27
|
+
resolve_config_root(
|
|
28
|
+
"http://machine.local:7862/gradio",
|
|
29
|
+
"https://machine.local:20443/gradio"
|
|
30
|
+
)
|
|
31
|
+
).toBe("https://machine.local:20443/gradio");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("keeps the configured origin for a remote root", () => {
|
|
35
|
+
expect(
|
|
36
|
+
resolve_config_root(
|
|
37
|
+
"https://remote.example/gradio",
|
|
38
|
+
"https://host.example/page"
|
|
39
|
+
)
|
|
40
|
+
).toBe("https://remote.example/gradio");
|
|
41
|
+
});
|
|
42
|
+
|
|
24
43
|
it("requests /config without a Content-Type header and with same-origin credentials, so the cross-origin embed fetch is not blocked by CORS", async () => {
|
|
25
44
|
let captured_init: RequestInit | undefined;
|
|
26
45
|
const fake_client = {
|
|
@@ -43,6 +62,53 @@ describe("resolve_config", () => {
|
|
|
43
62
|
expect(header_names).not.toContain("content-type");
|
|
44
63
|
expect(captured_init?.credentials).toBe("same-origin");
|
|
45
64
|
});
|
|
65
|
+
|
|
66
|
+
const in_browser = typeof window !== "undefined";
|
|
67
|
+
|
|
68
|
+
it.skipIf(!in_browser)(
|
|
69
|
+
"uses the browser origin for a same-host config root behind a proxy",
|
|
70
|
+
async () => {
|
|
71
|
+
const page = new URL(window.location.href);
|
|
72
|
+
const internal_root = `${page.protocol}//${page.hostname}:7862/gradio`;
|
|
73
|
+
window.gradio_config = {
|
|
74
|
+
...config_response,
|
|
75
|
+
root: internal_root
|
|
76
|
+
};
|
|
77
|
+
const fake_client = {
|
|
78
|
+
options: {},
|
|
79
|
+
deep_link: null
|
|
80
|
+
} as unknown as Client;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const config = await resolve_config.call(fake_client, internal_root);
|
|
84
|
+
expect(config?.root).toBe(`${page.origin}/gradio`);
|
|
85
|
+
} finally {
|
|
86
|
+
delete (window as Partial<Window>).gradio_config;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
it.skipIf(!in_browser)(
|
|
92
|
+
"keeps a remote config root when the page and backend hostnames differ",
|
|
93
|
+
async () => {
|
|
94
|
+
const remote_root = "https://remote.example/gradio";
|
|
95
|
+
window.gradio_config = {
|
|
96
|
+
...config_response,
|
|
97
|
+
root: remote_root
|
|
98
|
+
};
|
|
99
|
+
const fake_client = {
|
|
100
|
+
options: {},
|
|
101
|
+
deep_link: null
|
|
102
|
+
} as unknown as Client;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const config = await resolve_config.call(fake_client, remote_root);
|
|
106
|
+
expect(config?.root).toBe(remote_root);
|
|
107
|
+
} finally {
|
|
108
|
+
delete (window as Partial<Window>).gradio_config;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
);
|
|
46
112
|
});
|
|
47
113
|
|
|
48
114
|
describe("resolve_root", () => {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { Client } from "../client";
|
|
3
|
+
import type { Config } from "../types";
|
|
4
|
+
|
|
5
|
+
describe("Client.refresh", () => {
|
|
6
|
+
test("refreshes from the app root without stripping the current page again", async () => {
|
|
7
|
+
const client = new Client("https://example.test/app/page");
|
|
8
|
+
client.config = {
|
|
9
|
+
root: "https://example.test/app",
|
|
10
|
+
dependencies: []
|
|
11
|
+
} as unknown as Config;
|
|
12
|
+
|
|
13
|
+
const refreshed_config = {
|
|
14
|
+
root: "https://example.test/app",
|
|
15
|
+
api_prefix: "/gradio_api",
|
|
16
|
+
dependencies: [{ id: 4, api_name: "generate" }]
|
|
17
|
+
} as unknown as Config;
|
|
18
|
+
const resolve_config = vi.fn().mockResolvedValue(refreshed_config);
|
|
19
|
+
Reflect.set(client, "resolve_config", resolve_config);
|
|
20
|
+
client.view_api = vi.fn().mockResolvedValue({});
|
|
21
|
+
client.get_url_config = vi.fn().mockReturnValue(refreshed_config);
|
|
22
|
+
|
|
23
|
+
await client.refresh();
|
|
24
|
+
|
|
25
|
+
expect(resolve_config).toHaveBeenCalledWith(
|
|
26
|
+
"https://example.test/app",
|
|
27
|
+
false
|
|
28
|
+
);
|
|
29
|
+
expect(client.api_map).toEqual({ generate: 4 });
|
|
30
|
+
});
|
|
31
|
+
});
|