|
import { describe, test, expect, vi } from "vitest";
|
|
import { spy } from "tinyspy";
|
|
import { setupServer } from "msw/node";
|
|
import { http, HttpResponse } from "msw";
|
|
import type { client_return } from "@gradio/client";
|
|
import { Dependency, TargetMap } from "./types";
|
|
import {
|
|
process_frontend_fn,
|
|
create_target_meta,
|
|
determine_interactivity,
|
|
process_server_fn,
|
|
get_component
|
|
} from "./init";
|
|
|
|
describe("process_frontend_fn", () => {
|
|
test("empty source code returns null", () => {
|
|
const source = "";
|
|
|
|
const fn = process_frontend_fn(source, false, 1, 1);
|
|
expect(fn).toBe(null);
|
|
});
|
|
|
|
test("falsey source code returns null: false", () => {
|
|
const source = false;
|
|
|
|
const fn = process_frontend_fn(source, false, 1, 1);
|
|
expect(fn).toBe(null);
|
|
});
|
|
|
|
test("falsey source code returns null: undefined", () => {
|
|
const source = undefined;
|
|
|
|
const fn = process_frontend_fn(source, false, 1, 1);
|
|
expect(fn).toBe(null);
|
|
});
|
|
|
|
test("falsey source code returns null: null", () => {
|
|
const source = null;
|
|
|
|
const fn = process_frontend_fn(source, false, 1, 1);
|
|
expect(fn).toBe(null);
|
|
});
|
|
|
|
test("source code returns a function", () => {
|
|
const source = "(arg) => arg";
|
|
|
|
const fn = process_frontend_fn(source, false, 1, 1);
|
|
expect(typeof fn).toBe("function");
|
|
});
|
|
|
|
test("arrays of values can be passed to the generated function", async () => {
|
|
const source = "(arg) => arg";
|
|
|
|
const fn = process_frontend_fn(source, false, 1, 1);
|
|
if (fn) {
|
|
await expect(fn([1])).resolves.toEqual([1]);
|
|
}
|
|
});
|
|
|
|
test("arrays of many values can be passed", async () => {
|
|
const source = "(...args) => args";
|
|
|
|
const fn = process_frontend_fn(source, false, 1, 1);
|
|
if (fn) {
|
|
await expect(fn([1, 2, 3, 4, 5, 6])).resolves.toEqual([1, 2, 3, 4, 5, 6]);
|
|
}
|
|
});
|
|
|
|
test("The generated function returns a promise", () => {
|
|
const source = "(arg) => arg";
|
|
|
|
const fn = process_frontend_fn(source, false, 1, 1);
|
|
if (fn) {
|
|
expect(fn([1])).toBeInstanceOf(Promise);
|
|
}
|
|
});
|
|
|
|
test("The generated function is callable and returns the expected value", async () => {
|
|
const source = "(arg) => arg";
|
|
|
|
const fn = process_frontend_fn(source, false, 1, 1);
|
|
if (fn) {
|
|
await expect(fn([1])).resolves.toEqual([1]);
|
|
}
|
|
});
|
|
|
|
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 () => {
|
|
const source = "(arg) => arg";
|
|
|
|
const fn = process_frontend_fn(source, false, 1, 1);
|
|
if (fn) {
|
|
await expect(fn([1])).resolves.toEqual([1]);
|
|
}
|
|
});
|
|
|
|
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 () => {
|
|
const source = "(arg) => arg";
|
|
|
|
const fn = process_frontend_fn(source, false, 2, 2);
|
|
if (fn) {
|
|
await expect(fn([1])).resolves.toEqual(1);
|
|
}
|
|
});
|
|
|
|
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 () => {
|
|
const source = "(arg) => arg";
|
|
|
|
const fn = process_frontend_fn(source, true, 1, 1);
|
|
if (fn) {
|
|
await expect(fn([1])).resolves.toEqual([1]);
|
|
}
|
|
});
|
|
|
|
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 () => {
|
|
const source = "(arg) => arg";
|
|
|
|
const fn = process_frontend_fn(source, true, 2, 2);
|
|
if (fn) {
|
|
await expect(fn([1])).resolves.toEqual(1);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("create_target_meta", () => {
|
|
test("creates a target map", () => {
|
|
const targets: Dependency["targets"] = [
|
|
[1, "change"],
|
|
[2, "input"],
|
|
[3, "load"]
|
|
];
|
|
const fn_index = 0;
|
|
const target_map = {};
|
|
|
|
const result = create_target_meta(targets, fn_index, target_map);
|
|
expect(result).toEqual({
|
|
1: { change: [0] },
|
|
2: { input: [0] },
|
|
3: { load: [0] }
|
|
});
|
|
});
|
|
|
|
test("if the target already exists, it adds the new trigger to the list", () => {
|
|
const targets: Dependency["targets"] = [
|
|
[1, "change"],
|
|
[1, "input"],
|
|
[1, "load"]
|
|
];
|
|
const fn_index = 1;
|
|
const target_map: TargetMap = {
|
|
1: { change: [0] }
|
|
};
|
|
|
|
const result = create_target_meta(targets, fn_index, target_map);
|
|
expect(result).toEqual({
|
|
1: { change: [0, 1], input: [1], load: [1] }
|
|
});
|
|
});
|
|
|
|
test("if the trigger already exists, it adds the new function to the list", () => {
|
|
const targets: Dependency["targets"] = [
|
|
[1, "change"],
|
|
[2, "change"],
|
|
[3, "change"]
|
|
];
|
|
const fn_index = 1;
|
|
const target_map: TargetMap = {
|
|
1: { change: [0] },
|
|
2: { change: [0] },
|
|
3: { change: [0] }
|
|
};
|
|
|
|
const result = create_target_meta(targets, fn_index, target_map);
|
|
expect(result).toEqual({
|
|
1: { change: [0, 1] },
|
|
2: { change: [0, 1] },
|
|
3: { change: [0, 1] }
|
|
});
|
|
});
|
|
|
|
test("if the target and trigger already exist, it adds the new function to the list", () => {
|
|
const targets: Dependency["targets"] = [[1, "change"]];
|
|
const fn_index = 1;
|
|
const target_map: TargetMap = {
|
|
1: { change: [0] }
|
|
};
|
|
|
|
const result = create_target_meta(targets, fn_index, target_map);
|
|
expect(result).toEqual({
|
|
1: { change: [0, 1] }
|
|
});
|
|
});
|
|
|
|
test("if the target, trigger and function id already exist, it does not add duplicates", () => {
|
|
const targets: Dependency["targets"] = [[1, "change"]];
|
|
const fn_index = 0;
|
|
const target_map: TargetMap = {
|
|
1: { change: [0] }
|
|
};
|
|
|
|
const result = create_target_meta(targets, fn_index, target_map);
|
|
expect(result).toEqual({
|
|
1: { change: [0] }
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("determine_interactivity", () => {
|
|
test("returns true if the prop is interactive = true", () => {
|
|
const result = determine_interactivity(
|
|
0,
|
|
true,
|
|
"hi",
|
|
new Set([0]),
|
|
new Set([2])
|
|
);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
test("returns false if the prop is interactive = false", () => {
|
|
const result = determine_interactivity(
|
|
0,
|
|
false,
|
|
"hi",
|
|
new Set([0]),
|
|
new Set([2])
|
|
);
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
test("returns true if the component is an input", () => {
|
|
const result = determine_interactivity(
|
|
0,
|
|
undefined,
|
|
"hi",
|
|
new Set([0]),
|
|
new Set([2])
|
|
);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
test("returns true if the component is not an input or output and the component has no default value: empty string", () => {
|
|
const result = determine_interactivity(
|
|
2,
|
|
undefined,
|
|
"",
|
|
new Set([0]),
|
|
new Set([1])
|
|
);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
test("returns true if the component is not an input or output and the component has no default value: empty array", () => {
|
|
const result = determine_interactivity(
|
|
2,
|
|
undefined,
|
|
[],
|
|
new Set([0]),
|
|
new Set([1])
|
|
);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
test("returns true if the component is not an input or output and the component has no default value: boolean", () => {
|
|
const result = determine_interactivity(
|
|
2,
|
|
undefined,
|
|
false,
|
|
new Set([0]),
|
|
new Set([1])
|
|
);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
test("returns true if the component is not an input or output and the component has no default value: undefined", () => {
|
|
const result = determine_interactivity(
|
|
2,
|
|
undefined,
|
|
undefined,
|
|
new Set([0]),
|
|
new Set([1])
|
|
);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
test("returns true if the component is not an input or output and the component has no default value: null", () => {
|
|
const result = determine_interactivity(
|
|
2,
|
|
undefined,
|
|
null,
|
|
new Set([0]),
|
|
new Set([1])
|
|
);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
test("returns true if the component is not an input or output and the component has no default value: 0", () => {
|
|
const result = determine_interactivity(
|
|
2,
|
|
undefined,
|
|
0,
|
|
new Set([0]),
|
|
new Set([1])
|
|
);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
test("returns false if the component is not an input or output and the component has a default value", () => {
|
|
const result = determine_interactivity(
|
|
2,
|
|
undefined,
|
|
"hello",
|
|
new Set([0]),
|
|
new Set([1])
|
|
);
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("process_server_fn", () => {
|
|
test("returns an object", () => {
|
|
const result = process_server_fn(1, ["fn1", "fn2"], {} as any);
|
|
expect(result).toBeTypeOf("object");
|
|
});
|
|
|
|
test("returns an object with the correct keys", () => {
|
|
const result = process_server_fn(1, ["fn1", "fn2"], {} as any);
|
|
expect(Object.keys(result)).toEqual(["fn1", "fn2"]);
|
|
});
|
|
|
|
test("returns an object with the correct keys and values", () => {
|
|
const app = {
|
|
component_server: async (id: number, fn: string, args: any) => {
|
|
return args;
|
|
}
|
|
} as client_return;
|
|
|
|
const result = process_server_fn(1, ["fn1", "fn2"], app);
|
|
expect(Object.keys(result)).toEqual(["fn1", "fn2"]);
|
|
|
|
expect(result.fn1).toBeInstanceOf(Function);
|
|
expect(result.fn2).toBeInstanceOf(Function);
|
|
});
|
|
|
|
test("returned server functions should resolve to a promise", async () => {
|
|
const app = {
|
|
component_server: async (id: number, fn: string, args: any) => {
|
|
return args;
|
|
}
|
|
} as client_return;
|
|
|
|
const result = process_server_fn(1, ["fn1", "fn2"], app);
|
|
const response = result.fn1("hello");
|
|
expect(response).toBeInstanceOf(Promise);
|
|
});
|
|
|
|
test("the functions call the clients component_server function with the correct arguments ", async () => {
|
|
const mock = spy(async (id: number, fn: string, args: any) => {
|
|
return args;
|
|
});
|
|
const app = {
|
|
component_server: mock as any
|
|
} as client_return;
|
|
|
|
const result = process_server_fn(1, ["fn1", "fn2"], app as client_return);
|
|
const response = await result.fn1("hello");
|
|
expect(response).toBe("hello");
|
|
expect(mock.calls).toEqual([[1, "fn1", "hello"]]);
|
|
});
|
|
|
|
test("if there are no server functions, it returns an empty object", () => {
|
|
const result = process_server_fn(1, undefined, {} as any);
|
|
expect(result).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe("get_component", () => {
|
|
test("returns an object", () => {
|
|
const result = get_component("test-component-one", "class_id", "root", []);
|
|
expect(result.component).toBeTypeOf("object");
|
|
});
|
|
|
|
test("returns an object with the correct keys", () => {
|
|
const result = get_component("test-component-one", "class_id", "root", []);
|
|
expect(Object.keys(result)).toEqual([
|
|
"component",
|
|
"name",
|
|
"example_components"
|
|
]);
|
|
});
|
|
|
|
test("the component key is a promise", () => {
|
|
const result = get_component("test-component-one", "class_id", "root", []);
|
|
expect(result.component).toBeInstanceOf(Promise);
|
|
});
|
|
|
|
test("the resolved component key is an object", async () => {
|
|
const result = get_component("test-component-one", "class_id", "root", []);
|
|
const o = await result.component;
|
|
|
|
expect(o).toBeTypeOf("object");
|
|
});
|
|
|
|
test("getting the same component twice should return the same promise", () => {
|
|
const result = get_component("test-component-one", "class_id", "root", []);
|
|
const result_two = get_component(
|
|
"test-component-one",
|
|
"class_id",
|
|
"root",
|
|
[]
|
|
);
|
|
|
|
expect(result.component).toBe(result_two.component);
|
|
});
|
|
|
|
test("if example components are not provided, the example_components key is undefined", async () => {
|
|
const result = get_component("dataset", "class_id", "root", []);
|
|
expect(result.example_components).toBe(undefined);
|
|
});
|
|
|
|
test("if the type is not a dataset, the example_components key is undefined", async () => {
|
|
const result = get_component("test-component-one", "class_id", "root", []);
|
|
expect(result.example_components).toBe(undefined);
|
|
});
|
|
|
|
test("when the type is a dataset, returns an object with the correct keys and values and example components", () => {
|
|
const result = get_component(
|
|
"dataset",
|
|
"class_id",
|
|
"root",
|
|
[
|
|
{
|
|
type: "test-component-one",
|
|
component_class_id: "example_class_id",
|
|
id: 1,
|
|
props: {
|
|
value: "hi",
|
|
interactive: false
|
|
},
|
|
has_modes: false,
|
|
instance: {} as any,
|
|
component: {} as any
|
|
}
|
|
],
|
|
["test-component-one"]
|
|
);
|
|
expect(result.component).toBeTypeOf("object");
|
|
expect(result.example_components).toBeInstanceOf(Map);
|
|
});
|
|
|
|
test("when example components are returned, returns an object with the correct keys and values and example components", () => {
|
|
const result = get_component(
|
|
"dataset",
|
|
"class_id",
|
|
"root",
|
|
[
|
|
{
|
|
type: "test-component-one",
|
|
component_class_id: "example_class_id",
|
|
id: 1,
|
|
props: {
|
|
value: "hi",
|
|
interactive: false
|
|
},
|
|
has_modes: false,
|
|
instance: {} as any,
|
|
component: {} as any
|
|
}
|
|
],
|
|
["test-component-one"]
|
|
);
|
|
expect(result.example_components?.get("test-component-one")).toBeTypeOf(
|
|
"object"
|
|
);
|
|
expect(result.example_components?.get("test-component-one")).toBeInstanceOf(
|
|
Promise
|
|
);
|
|
});
|
|
|
|
test("if the component is not found then it should request the component from the server", async () => {
|
|
const api_url = "example.com";
|
|
const id = "test-random";
|
|
const variant = "component";
|
|
const handlers = [
|
|
http.get(`${api_url}/custom_component/${id}/${variant}/style.css`, () => {
|
|
return new HttpResponse('console.log("boo")', {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "text/css"
|
|
}
|
|
});
|
|
})
|
|
];
|
|
|
|
|
|
|
|
const { mock } = vi.hoisted(() => {
|
|
return { mock: vi.fn() };
|
|
});
|
|
|
|
vi.mock(
|
|
`example.com/custom_component/test-random/component/index.js`,
|
|
async () => {
|
|
mock();
|
|
return {
|
|
default: {
|
|
default: "HELLO"
|
|
}
|
|
};
|
|
}
|
|
);
|
|
|
|
const server = setupServer(...handlers);
|
|
server.listen();
|
|
|
|
await get_component("test-random", id, api_url, []).component;
|
|
|
|
expect(mock).toHaveBeenCalled();
|
|
|
|
server.close();
|
|
});
|
|
});
|
|
|