Back to Repositories

Testing Core Initialization Functions in Gradio-App

This test suite validates core initialization and processing functionality in the Gradio application, focusing on frontend function handling, component initialization, and server-side processing. The tests ensure robust component lifecycle management and proper handling of interactive elements.

Test Coverage Overview

The test suite provides comprehensive coverage of initialization functions including process_frontend_fn, create_target_meta, determine_interactivity, process_server_fn, and get_component.

Key functionality tested includes:
  • Frontend function processing and validation
  • Target mapping and dependency management
  • Component interactivity determination
  • Server-side function processing
  • Component initialization and caching

Implementation Analysis

The testing approach utilizes Jest/Vitest for unit testing with extensive use of mocking and spying capabilities. Tests follow a structured pattern of validating both positive and negative cases, with particular attention to edge cases and error conditions.

Technical implementation leverages TypeScript’s type system for robust testing of function signatures and return types, while making use of modern testing patterns like async/await for promise-based operations.

Technical Details

Testing tools and configuration:
  • Vitest as the test runner
  • TinySpy for function spying
  • MSW (Mock Service Worker) for API mocking
  • TypeScript for type checking
  • Custom test utilities for component simulation

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through thorough coverage of edge cases and comprehensive validation of component behavior.

Notable practices include:
  • Isolated unit tests with proper mocking
  • Comprehensive error case handling
  • Clear test descriptions and organization
  • Proper async testing patterns
  • Type-safe testing approaches

gradio-app/gradio

js/core/src/init.test.ts

            
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();
	});
});