unit tests for shared component functionality (#4794)

* add more tests

* fixes

* fixes

* fixes

* fix testmatch
This commit is contained in:
pngwn 2023-07-06 10:55:45 +01:00 committed by GitHub
parent 84d890fc1b
commit ea889fa8c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 395 additions and 21 deletions

View File

@ -3,7 +3,7 @@ export default {
screenshot: "only-on-failure",
trace: "retain-on-failure"
},
// testMatch: /.*.spec.ts/,
testMatch: /.*.spec.ts/,
testDir: "..",
globalSetup: "./playwright-setup.js"
};

View File

@ -19,7 +19,7 @@
export let scale: number | null = null;
export let min_width: number | undefined = undefined;
export let loading_status: LoadingStatus;
export let show_label: boolean;
export let show_label: boolean = true;
export let selectable: boolean = false;
const dispatch = createEventDispatcher<{ change: undefined }>();

View File

@ -0,0 +1,231 @@
import {
afterAll,
afterEach,
beforeAll,
beforeEach,
describe,
expect,
test
} from "vitest";
import { render, cleanup } from "@gradio/tootils";
import { setupi18n } from "../src/i18n";
import AnnotatedImage from "../src/components/AnnotatedImage/AnnotatedImage.svelte";
import Audio from "../src/components/Audio/Audio.svelte";
import Chatbot from "../src/components/Chatbot/Chatbot.svelte";
import Checkbox from "../src/components/Checkbox/Checkbox.svelte";
import CheckboxGroup from "../src/components/CheckboxGroup/CheckboxGroup.svelte";
import ColorPicker from "../src/components/ColorPicker/ColorPicker.svelte";
import DataFrame from "../src/components/DataFrame/DataFrame.svelte";
import Dropdown from "../src/components/Dropdown/Dropdown.svelte";
import File from "../src/components/File/File.svelte";
import Gallery from "../src/components/Gallery/Gallery.svelte";
import HTML from "../src/components/HTML/HTML.svelte";
import HighlightedText from "../src/components/HighlightedText/HighlightedText.svelte";
import Json from "../src/components/Json/Json.svelte";
import Label from "../src/components/Label/Label.svelte";
import Markdown from "../src/components/Markdown/Markdown.svelte";
import Model3D from "../src/components/Model3D/Model3D.svelte";
import Number from "../src/components/Number/Number.svelte";
import Radio from "../src/components/Radio/Radio.svelte";
import Slider from "../src/components/Slider/Slider.svelte";
import Textbox from "../src/components/Textbox/Textbox.svelte";
import TimeSeries from "../src/components/TimeSeries/TimeSeries.svelte";
import UploadButton from "../src/components/UploadButton/UploadButton.svelte";
import Video from "../src/components/Video/Video.svelte";
import { LoadingStatus } from "../src/components/StatusTracker/types";
const loading_status: LoadingStatus = {
eta: 0,
queue_position: 1,
queue_size: 1,
status: "complete",
scroll_to_output: false,
visible: true,
fn_index: 0,
show_progress: "full"
};
const components = [
["AnnotatedImage", AnnotatedImage, { height: 100, width: 100, value: null }],
["Audio", Audio, {}],
// ["Button", Button, {}],
["Chatbot", Chatbot, {}],
["Checkbox", Checkbox, {}],
["CheckboxGroup", CheckboxGroup, { choices: ["a", "b", "c"] }],
// ["Code", Code, {}],
["ColorPicker", ColorPicker, {}],
[
"DataFrame",
DataFrame,
{ value: [[1, 2, 3]], col_count: [3, "fixed"], row_count: [3, "fixed"] }
],
// ["Dataset", Dataset, {}],
["Dropdown", Dropdown, { choices: ["a", "b", "c"] }],
["File", File, {}],
["Gallery", Gallery, {}],
["HTML", HTML, {}],
["HighlightedText", HighlightedText, {}],
// ["Image",, {} Image],
["Json", Json, {}],
["Label", Label, {}],
["Markdown", Markdown, {}],
["Model3D", Model3D, {}],
["Number", Number, {}],
// ["Plot", Plot, {}],
["Radio", Radio, {}],
["Slider", Slider, {}],
["Textbox", Textbox, {}],
["TimeSeries", TimeSeries, {}],
["UploadButton", UploadButton, {}],
["Video", Video, {}]
] as const;
describe("all components should apply provided class names", () => {
beforeAll(() => {
setupi18n();
});
afterEach(() => {
cleanup();
});
components.forEach(([name, component, props]) => {
test(name, async () => {
const { container } = await render(component, {
...props,
loading_status,
elem_classes: ["test-class"]
});
const elem = container.querySelector(`.test-class`);
expect(elem).not.toBeNull();
});
});
["Button", "Code", "Image", "Plot"].forEach((name) => {
test.todo(name);
});
});
describe("all components should apply provided id", () => {
beforeAll(() => {
setupi18n();
});
afterEach(() => {
cleanup();
});
components.forEach(([name, component, props]) => {
test(name, async () => {
const { container } = await render(component, {
...props,
loading_status,
elem_id: "test-id"
});
const elem = container.querySelector(`#test-id`);
expect(elem).not.toBeNull();
});
});
["Button", "Code", "Image", "Plot"].forEach((name) => {
test.todo(name);
});
});
describe("all components should be invisible when visible=false", () => {
beforeAll(() => {
setupi18n();
});
afterEach(() => {
cleanup();
});
components.forEach(([name, component, props]) => {
test(name, async () => {
const { container } = await render(component, {
...props,
loading_status,
elem_id: "test-id",
visible: false
});
const elem = container.querySelector(`#test-id`);
expect(elem).toHaveClass("hide-container");
});
});
["Button", "Code", "Image", "Plot"].forEach((name) => {
test.todo(name);
});
});
describe("all components should have the appropriate label when set via the `label` prop", () => {
beforeAll(() => {
setupi18n();
});
afterEach(() => {
cleanup();
});
components
.filter(([name]) => name !== "Markdown" && name !== "HTML")
.forEach(([name, component, props]) => {
test(name, async () => {
const { getAllByText } = await render(component, {
...props,
loading_status,
label: name + "LABEL_TEST"
});
const elems = getAllByText(name + "LABEL_TEST");
expect(elems.length).toBeGreaterThan(0);
});
});
["Button", "Code", "Image", "Plot"].forEach((name) => {
test.todo(name);
});
});
describe("all components should hide their label when `show_label=false`", () => {
components
.filter(([name]) => name !== "Markdown" && name !== "HTML")
.forEach(([name, component, props]) => {
test.todo(name);
});
["Button", "Code", "Image", "Plot"].forEach((name) => {
test.todo(name);
});
});
describe("all components should show their label when `show_label=true`", () => {
components
.filter(([name]) => name !== "Markdown" && name !== "HTML")
.forEach(([name, component, props]) => {
test.todo(name);
});
["Button", "Code", "Image", "Plot"].forEach((name) => {
test.todo(name);
});
});
describe("all components should hide their container when `container=false`", () => {
components
.filter(([name]) => name !== "Markdown" && name !== "HTML")
.forEach(([name, component, props]) => {
test.todo(name);
});
["Button", "Code", "Image", "Plot"].forEach((name) => {
test.todo(name);
});
});

142
js/app/test/tests.md Normal file
View File

@ -0,0 +1,142 @@
# Test Coverage
Just a little reference docs to understand what is tested/ needs testing. Perhaps temporary until we are in a better place.
## Interface
## Flagging
## Blocks
## Block Layouts
## Themes
## Components
### Props/kwargs
| Component | `value` | `visible` | `elem_id` | `elem_classes` | `container` | `label` | `show_label` |
| --------------- | --------------- | ---------------- | ---------------- | ------------------- | ----------------- | --------- | ---------------|
| AnnotatedImage | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Audio | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| BarPlot | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Button | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Chatbot | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Checkbox | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| CheckboxGroup | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| ClearButton | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Code | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| ColorPicker | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Dataframe | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Dropdown | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| File | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Gallery | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| HTML | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| HighlightedText | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Image | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | 
| JSON | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Label | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Lineplot | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Markdown | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Model3D | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Number | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Plot | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Radio | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| ScatterPlot | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Slider | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Textbox | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Timeseries | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| UploadButton | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
| Video | `❌` | `✅` | `✅` | `✅` | `❌` | `✅` | `❌` |
### Events
| Component | `value` | `visible` | `elem_id` | `elem_classes` | `container` | `label` | `show_label` |
| --------------- | --------------- | ---------------- | ---------------- | ------------------- | ----------------- | --------- | ---------------|
| AnnotatedImage | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Audio | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| BarPlot | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Button | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Chatbot | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Checkbox | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| CheckboxGroup | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| ClearButton | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Code | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| ColorPicker | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Dataframe | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Dataset | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Dropdown | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| File | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Gallery | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| HTML | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| HighlightedText | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Image | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | 
| Interpretation | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| JSON | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Label | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Lineplot | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Markdown | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Model3D | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Number | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Plot | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Radio | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| ScatterPlot | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Slider | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| State | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Textbox | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Timeseries | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| UploadButton | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
| Video | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` | `❌` |
### `AnnotatedImage`
### `Audio`
### `BarPlot`
### `Button`
### `Chatbot`
### `Checkbox`
### `CheckboxGroup`
### `ClearButton`
### `Code`
### `ColorPicker`
### `Dataframe`
### `Dataset`
### `Dropdown`
### `File`
### `Gallery`
### `HTML`
### `HighlightedText`
### `Image`
### `Interpretation`
### `JSON`
### `Label`
### `Lineplot`
### `Markdown`
### `Model3D`
### `Number`
### `Plot`
### `Radio`
### `ScatterPlot`
### `Slider`
### `State`
### `Textbox`
### `Timeseries`
### `UploadButton`
### `Video`
## Helpers
### Error
### load
### Examples
### Progress
### update
### make_waveform
### EventData
## Routes
### `Request`
### `mount_gradio_app`
## Clients
### Python (`gradio_client`)
### JavaScript (`@gradio/client`)

View File

@ -145,6 +145,7 @@ export default defineConfig(({ mode }) => {
handle_ce_css()
],
test: {
setupFiles: [resolve(__dirname, "../../.config/setup_vite_tests.ts")],
environment: TEST_MODE,
include:
TEST_MODE === "node"

View File

@ -11,7 +11,7 @@
<button
on:click
class:hide={!visible}
class:hide-container={!visible}
class="{size} {variant} {elem_classes.join(' ')}"
style:flex-grow={scale}
style:width={scale === 0 ? "fit-content" : null}
@ -49,7 +49,7 @@
cursor: not-allowed;
}
.hide {
.hide-container {
display: none;
}

View File

@ -2,25 +2,25 @@
import path from "path";
import { loadPyodide, PyodideInterface } from "pyodide";
import { describe, it, expect, beforeEach } from "vitest";
import { describe, it, expect, beforeEach, beforeAll } from "vitest";
import { writeFileWithParents, renameWithParents } from "./file";
describe("writeFileWithParents()", () => {
let pyodide: PyodideInterface;
beforeEach(async () => {
beforeAll(async () => {
pyodide = await loadPyodide({
indexURL: path.resolve(__dirname, "../../node_modules/pyodide"),
indexURL: path.resolve(__dirname, "../../node_modules/pyodide")
});
});
const testCases: { paths: string[] }[] = [
{ paths: ["foo.py"] },
{ paths: ["foo/bar.py"] },
{ paths: ["foo/bar.py", "foo/hoge.py"] },
{ paths: ["foo/bar/baz.py"] },
{ paths: ["foo/bar/baz.py", "foo/bar/hoge.py"] },
{ paths: ["/foo.py"] },
{ paths: ["foo/bar/baz.py", "foo/hoge.py"] },
{ paths: ["foo/bar/baz/pii.py"] },
{ paths: ["foo/bar/baz/boo.py", "foo/bar/hoge.py"] },
{ paths: ["/boo/foo.py"] }
];
testCases.forEach(({ paths }) => {
it(`writes files (${paths})`, () => {
@ -48,9 +48,9 @@ describe("writeFileWithParents()", () => {
describe("renameWithParents", () => {
let pyodide: PyodideInterface;
beforeEach(async () => {
beforeAll(async () => {
pyodide = await loadPyodide({
indexURL: path.resolve(__dirname, "../../node_modules/pyodide"),
indexURL: path.resolve(__dirname, "../../node_modules/pyodide")
});
});
@ -59,7 +59,7 @@ describe("renameWithParents", () => {
{ oldPath: "foo.py", newPath: "bar/baz.py" }, // To a nested dir
{ oldPath: "baz/foo.py", newPath: "bar.py" }, // From a nested dir
{ oldPath: "foo/bar.py", newPath: "foo/baz.py" }, // Same dir with a parent path
{ oldPath: "foo/bar.py", newPath: "baz/qux.py" }, // With parent paths, different dirs
{ oldPath: "foo/bar.py", newPath: "baz/qux.py" } // With parent paths, different dirs
];
testCases.forEach(({ oldPath, newPath }) => {
it(`renames "${oldPath}" to "${newPath}"`, () => {

View File

@ -4,16 +4,16 @@ import { verifyRequirements } from "./requirements";
describe("verifyRequirements", () => {
const allowedRequirements = [
[
"http://files.pythonhosted.org/packages/62/9c/0467dea0a064a998f94c33d03988f33efc744de1a2a550b56b38910cafa2/streamlit-1.13.0-py2.py3-none-any.whl",
"http://files.pythonhosted.org/packages/62/9c/0467dea0a064a998f94c33d03988f33efc744de1a2a550b56b38910cafa2/streamlit-1.13.0-py2.py3-none-any.whl"
],
[
"https://files.pythonhosted.org/packages/62/9c/0467dea0a064a998f94c33d03988f33efc744de1a2a550b56b38910cafa2/streamlit-1.13.0-py2.py3-none-any.whl",
],
"https://files.pythonhosted.org/packages/62/9c/0467dea0a064a998f94c33d03988f33efc744de1a2a550b56b38910cafa2/streamlit-1.13.0-py2.py3-none-any.whl"
]
];
allowedRequirements.forEach((requirements) => {
it(`allows http: and https: schemes (requirements=${JSON.stringify(
requirements
)})`, () => {
it(`allows http: and https: schemes (requirements=${
requirements[0].split(":")[0]
})`, () => {
expect(() => verifyRequirements(requirements)).not.toThrow();
});
});

View File

@ -17,7 +17,7 @@
"lint": "ESLINT_USE_FLAT_CONFIG=true eslint -c .config/eslint.config.js js client/js",
"ts:check": "svelte-check --tsconfig tsconfig.json",
"test": "pnpm --filter @gradio/client build && vitest dev --config .config/vitest.config.ts",
"test:run": "pnpm --filter @gradio/client build && vitest run --config .config/vitest.config.ts",
"test:run": "pnpm --filter @gradio/client build && vitest run --config .config/vitest.config.ts --reporter=verbose",
"test:node": "TEST_MODE=node pnpm vitest run --config .config/vitest.config.ts",
"test:browser": "pnpm --filter @gradio/app test:browser",
"test:browser:full": "run-s build test:browser",