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}/client/${variant}/style.css`, () => { return new HttpResponse('console.log("boo")', { status: 200, headers: { "Content-Type": "text/css" } }); } ) ]; // vi.mock calls are always hoisted out of the test function to the top of the file // so we need to use vi.hoisted to hoist the mock function above the vi.mock call const { mock } = vi.hoisted(() => { return { mock: vi.fn() }; }); vi.mock( `example.com/custom_component/test-random/client/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(); }); });