mirror of
https://github.com/gradio-app/gradio.git
synced 2025-04-12 12:40:29 +08:00
add test infra + add browser tests to CI (#852)
* add test infra * improve test setup and utils * finish a test * add browser tests to ci * fix ci * fix ci * fix ci * fix ci * debug ci * debug ci * debug ci * debug ci * debug ci * debug ci * debug ci * fix ci * update lockfile * fix formatting * install browser when not cached * bust cache * debug test in ci * fix button label * generate screenshots for failed tests * generate screenshots for failed tests * generate screenshots for failed tests * fix tests * clean uip debug logs * add setuip + teardown to functional tests * remove build from static checks
This commit is contained in:
parent
398f5560d1
commit
26d2c190fc
54
.github/workflows/ui.yml
vendored
54
.github/workflows/ui.yml
vendored
@ -2,8 +2,8 @@ name: gradio-ui
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "ui/**"
|
||||
pull_request:
|
||||
@ -12,23 +12,30 @@ on:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./ui
|
||||
working-directory: ui
|
||||
|
||||
env:
|
||||
CI: true
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
||||
|
||||
concurrency:
|
||||
group: deploy-${{ github.ref }}-${{ github.event_name == 'push' || github.event.inputs.fire != null }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check:
|
||||
quick-checks:
|
||||
name: static checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: npm i -g pnpm@6
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2.2.1
|
||||
with:
|
||||
version: 6
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
cache: pnpm
|
||||
cache-dependency-path: ui/pnpm-lock.yaml
|
||||
- name: install dependencies
|
||||
run: pnpm i --frozen-lockfile
|
||||
- name: formatting check
|
||||
@ -38,5 +45,32 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: unit tests
|
||||
run: pnpm test:run
|
||||
- name: build
|
||||
run: pnpm build
|
||||
functional-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2.2.1
|
||||
with:
|
||||
version: 6
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: pnpm
|
||||
cache-dependency-path: ui/pnpm-lock.yaml
|
||||
- name: Cache browsers
|
||||
id: browser_cache
|
||||
uses: actions/cache@main
|
||||
with:
|
||||
path: "~/.cache/ms-playwright"
|
||||
key: chromium-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpx playwright install chromium
|
||||
- run: pnpm build
|
||||
- run: pnpm test:browser
|
||||
- name: Upload failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
retention-days: 3
|
||||
name: test-failure-${{ github.run_id }}
|
||||
path: ui/packages/app/test-results
|
||||
|
126
demo/xray_blocks/config.json
Normal file
126
demo/xray_blocks/config.json
Normal file
@ -0,0 +1,126 @@
|
||||
{
|
||||
"mode": "blocks",
|
||||
"components": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "markdown",
|
||||
"props": {
|
||||
"value": "<pre><code># Detect Disease From Scan\nWith this model you can lorem ipsum\n- ipsum 1\n- ipsum 2\n</code></pre>\n",
|
||||
"name": "markdown",
|
||||
"label": null,
|
||||
"css": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "checkboxgroup",
|
||||
"props": {
|
||||
"choices": ["Covid", "Malaria", "Lung Cancer"],
|
||||
"default": [],
|
||||
"name": "checkboxgroup",
|
||||
"label": "Disease to Scan For",
|
||||
"css": {}
|
||||
}
|
||||
},
|
||||
{ "id": 3, "type": "tabs", "props": null },
|
||||
{ "id": 4, "type": "tabitem", "props": { "label": "X-ray" } },
|
||||
{ "id": 5, "type": "row", "props": { "type": "row" } },
|
||||
{
|
||||
"id": 6,
|
||||
"type": "image",
|
||||
"props": {
|
||||
"image_mode": "RGB",
|
||||
"shape": null,
|
||||
"source": "upload",
|
||||
"tool": "editor",
|
||||
"name": "image",
|
||||
"label": null,
|
||||
"css": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "json",
|
||||
"props": { "name": "json", "label": null, "css": {} }
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "button",
|
||||
"props": {
|
||||
"value": "Run",
|
||||
"name": "button",
|
||||
"label": null,
|
||||
"css": { "background-color": "red", "--hover-color": "orange" }
|
||||
}
|
||||
},
|
||||
{ "id": 9, "type": "tabitem", "props": { "label": "CT Scan" } },
|
||||
{ "id": 10, "type": "row", "props": { "type": "row" } },
|
||||
{
|
||||
"id": 11,
|
||||
"type": "image",
|
||||
"props": {
|
||||
"image_mode": "RGB",
|
||||
"shape": null,
|
||||
"source": "upload",
|
||||
"tool": "editor",
|
||||
"name": "image",
|
||||
"label": null,
|
||||
"css": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "json",
|
||||
"props": { "name": "json", "label": null, "css": {} }
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "button",
|
||||
"props": { "value": "Run", "name": "button", "label": null, "css": {} }
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "textbox",
|
||||
"props": {
|
||||
"lines": 1,
|
||||
"placeholder": null,
|
||||
"default": "",
|
||||
"name": "textbox",
|
||||
"label": null,
|
||||
"css": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"theme": "default",
|
||||
"layout": {
|
||||
"id": 0,
|
||||
"children": [
|
||||
{ "id": 1 },
|
||||
{ "id": 2 },
|
||||
{
|
||||
"id": 3,
|
||||
"children": [
|
||||
{
|
||||
"id": 4,
|
||||
"children": [
|
||||
{ "id": 5, "children": [{ "id": 6 }, { "id": 7 }] },
|
||||
{ "id": 8 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"children": [
|
||||
{ "id": 10, "children": [{ "id": 11 }, { "id": 12 }] },
|
||||
{ "id": 13 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": 14 }
|
||||
]
|
||||
},
|
||||
"dependencies": [
|
||||
{ "targets": [8], "trigger": "click", "inputs": [2, 6], "outputs": [7] },
|
||||
{ "targets": [13], "trigger": "click", "inputs": [2, 11], "outputs": [12] }
|
||||
]
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
from textwrap import indent
|
||||
import gradio as gr
|
||||
|
||||
import random
|
||||
@ -38,4 +39,5 @@ with xray_blocks:
|
||||
|
||||
overall_probability = gr.components.Textbox()
|
||||
|
||||
print(xray_blocks.get_config_file())
|
||||
xray_blocks.launch()
|
||||
|
@ -45,9 +45,9 @@
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script>
|
||||
<title>Gradio</title>
|
||||
<script type="module" crossorigin src="./assets/index.b8adfcc9.js"></script>
|
||||
<link rel="modulepreload" href="./assets/vendor.cb9b505c.js">
|
||||
<link rel="stylesheet" href="./assets/index.7e32d9ef.css">
|
||||
<script type="module" crossorigin src="./assets/index.cdad49d2.js"></script>
|
||||
<link rel="modulepreload" href="./assets/vendor.90013e04.js">
|
||||
<link rel="stylesheet" href="./assets/index.778d40cb.css">
|
||||
</head>
|
||||
|
||||
<body style="height: 100%; margin: 0; padding: 0">
|
||||
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"interface": {
|
||||
"submit": "Submit",
|
||||
"clear": "Clear",
|
||||
"interpret": "Interpret",
|
||||
"flag": "Flag",
|
||||
"examples": "Examples",
|
||||
"drop_image": "Drop Image Here",
|
||||
"drop_video": "Drop Video Here",
|
||||
"drop_audio": "Drop Audio Here",
|
||||
"drop_file": "Drop File Here",
|
||||
"drop_csv": "Drop CSV Here",
|
||||
"or": "or",
|
||||
"click_to_upload": "Click to Upload",
|
||||
"view_api": "view the api",
|
||||
"built_with_Gradio": "built with gradio"
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"interface": {
|
||||
"submit": "Enviar",
|
||||
"clear": "Limpiar",
|
||||
"interpret": "Interpretar",
|
||||
"flag": "Avisar",
|
||||
"examples": "Ejemplos",
|
||||
"drop_image": "Coloque la imagen aquí",
|
||||
"drop_video": "Coloque el video aquí",
|
||||
"drop_audio": "Coloque el audio aquí",
|
||||
"drop_file": "Coloque el archivo aquí",
|
||||
"drop_csv": "Coloque el CSV aquí",
|
||||
"or": "o",
|
||||
"click_to_upload": "Haga click para cargar",
|
||||
"view_api": "Ver la API",
|
||||
"built_with_Gradio": "Construido con Gradio"
|
||||
}
|
||||
}
|
2
ui/.gitignore
vendored
2
ui/.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
node_modules
|
||||
public/build/
|
||||
|
||||
test-results
|
||||
|
||||
|
@ -10,23 +10,38 @@
|
||||
"format:write": "prettier --write --plugin-search-dir=. .",
|
||||
"ts:check": "svelte-check --tsconfig tsconfig.json",
|
||||
"test": "vitest dev",
|
||||
"test:run": "vitest run"
|
||||
"test:run": "vitest run",
|
||||
"test:browser": "pnpm test:browser --filter @gradio/app",
|
||||
"test:browser:full": "run-s build test:browser",
|
||||
"test:browser:debug": "pnpm test:browser:debug --filter @gradio/app"
|
||||
},
|
||||
"type": "module",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"autoprefixer": "^9.8.8",
|
||||
"@gradio/tootils": "workspace:^0.0.1",
|
||||
"@playwright/test": "^1.20.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.36",
|
||||
"@testing-library/dom": "^8.11.3",
|
||||
"@testing-library/svelte": "^3.1.0",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"happy-dom": "^2.49.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"polka": "^1.0.0-next.22",
|
||||
"postcss": "^8.4.5",
|
||||
"postcss-nested": "^5.0.6",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier-plugin-svelte": "^2.6.0",
|
||||
"sirv": "^2.0.2",
|
||||
"sirv-cli": "^2.0.2",
|
||||
"svelte": "^3.46.3",
|
||||
"svelte-check": "^2.4.1",
|
||||
"svelte-i18n": "^3.3.13",
|
||||
"svelte-preprocess": "^4.10.1",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"vitest": "^0.3.2"
|
||||
"tinyspy": "^0.3.0",
|
||||
"vite": "^2.7.13",
|
||||
"vitest": "^0.7.4"
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,9 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.36",
|
||||
"vite": "^2.7.13"
|
||||
"preview": "vite preview",
|
||||
"test:browser": "pnpx playwright test test/ --config=../../playwright.config.js",
|
||||
"test:browser:debug": "pnpx playwright test test/ --debug"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gradio/audio": "workspace:^0.0.1",
|
||||
@ -30,7 +28,6 @@
|
||||
"@gradio/theme": "workspace:^0.0.1",
|
||||
"@gradio/upload": "workspace:^0.0.1",
|
||||
"@gradio/video": "workspace:^0.0.1",
|
||||
"autoprefixer": "^9.8.8",
|
||||
"mime-types": "^2.1.34",
|
||||
"svelte-i18n": "^3.3.13"
|
||||
}
|
||||
|
@ -67,7 +67,7 @@
|
||||
const c = await component_map[name]();
|
||||
res({ name, component: c });
|
||||
} catch (e) {
|
||||
console.log(name)
|
||||
console.log(name);
|
||||
rej(e);
|
||||
}
|
||||
});
|
||||
|
@ -2,7 +2,6 @@
|
||||
import { Button } from "@gradio/button";
|
||||
|
||||
export let value: string;
|
||||
export let label: string;
|
||||
export let style: string | null;
|
||||
export let variant: "primary" | "secondary" = "primary";
|
||||
</script>
|
||||
|
23
ui/packages/app/src/components/Button/Button.test.ts
Normal file
23
ui/packages/app/src/components/Button/Button.test.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { test, describe, assert, afterAll } from "vitest";
|
||||
import { spy } from "tinyspy";
|
||||
import { cleanup, fireEvent, render } from "@gradio/tootils";
|
||||
|
||||
import Button from "./Button.svelte";
|
||||
|
||||
describe("Hello.svelte", () => {
|
||||
afterAll(() => cleanup());
|
||||
const { container, component } = render(Button, { value: "Click Me" });
|
||||
|
||||
test("renders label text", () => {
|
||||
assert.equal(container.innerText, "Click Me");
|
||||
});
|
||||
|
||||
test("triggers callback when clicked", async () => {
|
||||
const mock = spy();
|
||||
component.$on("click", mock);
|
||||
|
||||
fireEvent.click(container.querySelector("button")!);
|
||||
|
||||
assert.isTrue(mock.called);
|
||||
});
|
||||
});
|
15
ui/packages/app/src/components/Carousel/Carousel.test.svelte
Normal file
15
ui/packages/app/src/components/Carousel/Carousel.test.svelte
Normal file
@ -0,0 +1,15 @@
|
||||
<script>
|
||||
import Carousel from "./Carousel.svelte";
|
||||
import CarouselItem from "../CarouselItem/CarouselItem.svelte";
|
||||
import api_logo from "../../../public/static/img/api-logo.svg";
|
||||
</script>
|
||||
|
||||
<Carousel on:change>
|
||||
<CarouselItem>
|
||||
<h1>Item 1</h1>
|
||||
</CarouselItem>
|
||||
|
||||
<CarouselItem>
|
||||
<img src={api_logo} alt="" />
|
||||
</CarouselItem>
|
||||
</Carousel>
|
52
ui/packages/app/src/components/Carousel/Carousel.test.ts
Normal file
52
ui/packages/app/src/components/Carousel/Carousel.test.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { test, describe, assert, afterEach } from "vitest";
|
||||
import { spy } from "tinyspy";
|
||||
import { cleanup, fireEvent, render } from "@gradio/tootils";
|
||||
|
||||
import Carousel from "./Carousel.test.svelte";
|
||||
|
||||
describe("Carousel + CarouselItem", () => {
|
||||
afterEach(() => cleanup());
|
||||
|
||||
test("renders first carousel item is rendered by default", () => {
|
||||
const { container } = render(Carousel);
|
||||
const item = container.querySelector(".component")!;
|
||||
|
||||
assert.equal(item.children[0].tagName, "H1");
|
||||
assert.equal(item.children[0].innerHTML, "Item 1");
|
||||
});
|
||||
|
||||
test("clicking next shows the second component", async () => {
|
||||
const { container } = render(Carousel);
|
||||
const [, next] = Array.from(container.querySelectorAll("button"));
|
||||
|
||||
await fireEvent.click(next);
|
||||
const item = container.querySelector(".component")!;
|
||||
|
||||
assert.equal(item.children[0].tagName, "IMG");
|
||||
});
|
||||
|
||||
test("clicking previous from index 0 shows the last component", async () => {
|
||||
const { container } = render(Carousel);
|
||||
const [previous] = Array.from(container.querySelectorAll("button"));
|
||||
|
||||
await fireEvent.click(previous);
|
||||
const item = container.querySelector(".component")!;
|
||||
|
||||
assert.equal(item.children[0].tagName, "IMG");
|
||||
});
|
||||
|
||||
test("change callback is triggered when", async () => {
|
||||
const { container, component } = render(Carousel);
|
||||
const [previous, next] = Array.from(container.querySelectorAll("button"));
|
||||
|
||||
const mock = spy();
|
||||
|
||||
component.$on("change", mock);
|
||||
|
||||
fireEvent.click(next);
|
||||
fireEvent.click(previous);
|
||||
|
||||
assert.isTrue(mock.called);
|
||||
assert.equal(mock.callCount, 2);
|
||||
});
|
||||
});
|
48
ui/packages/app/src/components/Chatbot/Chatbot.test.ts
Normal file
48
ui/packages/app/src/components/Chatbot/Chatbot.test.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { test, describe, assert, afterEach } from "vitest";
|
||||
import { cleanup, render, get_text } from "@gradio/tootils";
|
||||
|
||||
import Chatbot from "./Chatbot.svelte";
|
||||
|
||||
describe("Chatbot", () => {
|
||||
afterEach(() => cleanup());
|
||||
|
||||
test("renders bot and user messages", () => {
|
||||
const { getAllByTestId } = render(Chatbot, {
|
||||
value: [["bot message one", "user message one"]]
|
||||
});
|
||||
|
||||
const bot = getAllByTestId("bot")[0];
|
||||
const user = getAllByTestId("user")[0];
|
||||
|
||||
assert.equal(get_text(bot), "bot message one");
|
||||
assert.equal(get_text(user), "user message one");
|
||||
});
|
||||
|
||||
test("renders additional message as they are passed", async () => {
|
||||
const { component, getAllByTestId } = render(Chatbot, {
|
||||
value: [["bot message one", "user message one"]]
|
||||
});
|
||||
|
||||
const bot = getAllByTestId("bot");
|
||||
const user = getAllByTestId("user");
|
||||
|
||||
assert.equal(bot.length, 1);
|
||||
assert.equal(user.length, 1);
|
||||
|
||||
await component.$set({
|
||||
value: [
|
||||
["bot message one", "user message one"],
|
||||
["bot message two", "user message two"]
|
||||
]
|
||||
});
|
||||
|
||||
const bot_2 = getAllByTestId("bot");
|
||||
const user_2 = getAllByTestId("user");
|
||||
|
||||
assert.equal(bot_2.length, 2);
|
||||
assert.equal(user_2.length, 2);
|
||||
|
||||
assert.equal(get_text(bot_2[1]), "bot message two");
|
||||
assert.equal(get_text(user_2[1]), "user message two");
|
||||
});
|
||||
});
|
@ -13,5 +13,4 @@
|
||||
<th>{header}</th>
|
||||
{/each}
|
||||
</thead>
|
||||
|
||||
</table>
|
||||
|
@ -121,7 +121,7 @@ window.launchGradioFromSpaces = async (space: string, target: string) => {
|
||||
};
|
||||
|
||||
async function get_config() {
|
||||
if (BUILD_MODE === "dev") {
|
||||
if (BUILD_MODE === "dev" || location.origin === "http://localhost:3000") {
|
||||
let config = await fetch(BACKEND_URL + "config");
|
||||
config = await config.json();
|
||||
return config;
|
||||
|
78
ui/packages/app/test/blocks-basic.spec.ts
Normal file
78
ui/packages/app/test/blocks-basic.spec.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { test, expect, Page } from "@playwright/test";
|
||||
|
||||
function mock_demo(page: Page, demo: string) {
|
||||
return page.route("http://localhost:7860/config", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
path: `../../../demo/${demo}/config.json`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mock_api(page: Page, body: Array<unknown>) {
|
||||
return page.route("http://localhost:7860/api/predict/", (route) => {
|
||||
const id = JSON.parse(route.request().postData()!).fn_index;
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: body[id]
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test("renders the correct elements", async ({ page }) => {
|
||||
await mock_demo(page, "xray_blocks");
|
||||
await page.goto("http://localhost:3000");
|
||||
|
||||
const description = await page.locator(".output-markdown");
|
||||
await expect(description).toContainText("Detect Disease From Scan");
|
||||
|
||||
const checkboxes = await page.locator(".input-checkbox-group");
|
||||
await expect(checkboxes).toContainText("Covid Malaria Lung Cancer");
|
||||
|
||||
const tabs = await page.locator("button", { hasText: /X-ray|CT Scan/ });
|
||||
await expect(tabs).toHaveCount(2);
|
||||
});
|
||||
|
||||
test("can run an api request and display the data", async ({ page }) => {
|
||||
await mock_demo(page, "xray_blocks");
|
||||
await mock_api(page, [
|
||||
[
|
||||
{
|
||||
Covid: 0.75,
|
||||
"Lung Cancer": 0.25
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
Covid: 0.75,
|
||||
"Lung Cancer": 0.25
|
||||
}
|
||||
]
|
||||
]);
|
||||
|
||||
await page.goto("http://localhost:3000");
|
||||
|
||||
await page.locator('button:has-text("Covid")').click();
|
||||
await page.locator('button:has-text("Lung Cancer")').click();
|
||||
|
||||
const run_button = await page.locator("button", { hasText: /Run/ });
|
||||
|
||||
await Promise.all([
|
||||
run_button.click(),
|
||||
page.waitForResponse("http://localhost:7860/api/predict/")
|
||||
]);
|
||||
|
||||
const json = await page.locator(".output-json");
|
||||
await expect(await json.innerText()).toContain(
|
||||
`{
|
||||
Covid: 0.75,
|
||||
Lung Cancer: 0.25
|
||||
}`
|
||||
);
|
||||
});
|
@ -4,7 +4,7 @@ import sveltePreprocess from "svelte-preprocess";
|
||||
|
||||
// this is dupe config, gonna try fix this
|
||||
import tailwind from "tailwindcss";
|
||||
import nested from "tailwindcss/nesting";
|
||||
import nested from "tailwindcss/nesting/index.js";
|
||||
|
||||
//@ts-ignore
|
||||
export default defineConfig(({ mode }) => {
|
||||
@ -17,9 +17,7 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
define: {
|
||||
BUILD_MODE: production ? JSON.stringify("prod") : JSON.stringify("dev"),
|
||||
BACKEND_URL: production
|
||||
? JSON.stringify("")
|
||||
: JSON.stringify("http://localhost:7860/")
|
||||
BACKEND_URL: JSON.stringify("http://localhost:7860/")
|
||||
},
|
||||
css: {
|
||||
postcss: {
|
||||
@ -28,10 +26,15 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
plugins: [
|
||||
svelte({
|
||||
hot: !process.env.VITEST,
|
||||
preprocess: sveltePreprocess({
|
||||
postcss: { plugins: [tailwind, nested] }
|
||||
})
|
||||
})
|
||||
]
|
||||
],
|
||||
test: {
|
||||
environment: "happy-dom",
|
||||
include: ["**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"]
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -15,4 +15,4 @@
|
||||
button {
|
||||
@apply hover:bg-gray-200;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -24,10 +24,14 @@
|
||||
>
|
||||
<div class="flex flex-col items-end space-y-4 p-3">
|
||||
{#each value as message}
|
||||
<div class="px-3 py-2 rounded-2xl bg-yellow-500 text-white ml-7">
|
||||
<div
|
||||
data-testid="bot"
|
||||
class="px-3 py-2 rounded-2xl bg-yellow-500 text-white ml-7"
|
||||
>
|
||||
{message[0]}
|
||||
</div>
|
||||
<div
|
||||
data-testid="user"
|
||||
class="px-3 py-2 rounded-2xl place-self-start bg-gray-300 dark:bg-gray-850 dark:text-gray-200 mr-7"
|
||||
>
|
||||
{message[1]}
|
||||
|
@ -2,7 +2,6 @@
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import "./typography.css";
|
||||
|
||||
|
||||
export let value: string;
|
||||
export let theme: string = "default";
|
||||
|
||||
|
11
ui/packages/tootils/README.md
Normal file
11
ui/packages/tootils/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# `@gradio/button`
|
||||
|
||||
```html
|
||||
<script>
|
||||
import { Button } from "@gradio/button";
|
||||
</script>
|
||||
|
||||
<button type="primary|secondary" href="string" on:click="{e.detail === href}">
|
||||
content
|
||||
</button>
|
||||
```
|
10
ui/packages/tootils/package.json
Normal file
10
ui/packages/tootils/package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@gradio/tootils",
|
||||
"version": "0.0.1",
|
||||
"description": "Internal test utilities",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"private": true
|
||||
}
|
5
ui/packages/tootils/src/index.ts
Normal file
5
ui/packages/tootils/src/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export function get_text<T extends HTMLElement>(el: T) {
|
||||
return el.innerText.trim();
|
||||
}
|
||||
|
||||
export * from "./render";
|
65
ui/packages/tootils/src/render.ts
Normal file
65
ui/packages/tootils/src/render.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { getQueriesForElement, prettyDOM } from "@testing-library/dom";
|
||||
import type { SvelteComponentTyped } from "svelte";
|
||||
|
||||
const containerCache = new Map();
|
||||
const componentCache = new Set();
|
||||
|
||||
type Component<T extends SvelteComponentTyped, Props> = new (args: {
|
||||
target: any;
|
||||
props?: Props;
|
||||
}) => T;
|
||||
|
||||
function render<Props, T extends SvelteComponentTyped<Props>>(
|
||||
Component: Component<T, Props> | { default: Component<T, Props> },
|
||||
props?: Props
|
||||
) {
|
||||
const container = document.body;
|
||||
const target = container.appendChild(document.createElement("div"));
|
||||
|
||||
const ComponentConstructor: Component<T, Props> =
|
||||
//@ts-ignore
|
||||
Component.default || Component;
|
||||
|
||||
const component = new ComponentConstructor({
|
||||
target,
|
||||
props
|
||||
});
|
||||
|
||||
containerCache.set(container, { target, component });
|
||||
componentCache.add(component);
|
||||
|
||||
component.$$.on_destroy.push(() => {
|
||||
componentCache.delete(component);
|
||||
});
|
||||
|
||||
return {
|
||||
container,
|
||||
component,
|
||||
debug: (el = container) => console.log(prettyDOM(el)),
|
||||
unmount: () => {
|
||||
if (componentCache.has(component)) component.$destroy();
|
||||
},
|
||||
...getQueriesForElement(container)
|
||||
};
|
||||
}
|
||||
|
||||
const cleanupAtContainer = (container: HTMLElement) => {
|
||||
const { target, component } = containerCache.get(container);
|
||||
|
||||
if (componentCache.has(component)) component.$destroy();
|
||||
|
||||
if (target.parentNode === document.body) {
|
||||
document.body.removeChild(target);
|
||||
}
|
||||
|
||||
containerCache.delete(container);
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
Array.from(containerCache.keys()).forEach(cleanupAtContainer);
|
||||
};
|
||||
|
||||
export * from "@testing-library/dom";
|
||||
|
||||
export { render, cleanup };
|
||||
export { fireEvent } from "@testing-library/svelte";
|
@ -7,8 +7,12 @@
|
||||
];
|
||||
|
||||
setTimeout(() => {
|
||||
messages = [...messages, ["message five", "message six"]];
|
||||
messages = [...messages, ["message five", ""]];
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
messages = [...messages, ["message five", "message six"]];
|
||||
}, 2000);
|
||||
</script>
|
||||
|
||||
<ChatBot value={messages} />
|
||||
|
21
ui/playwright-setup.js
Normal file
21
ui/playwright-setup.js
Normal file
@ -0,0 +1,21 @@
|
||||
import polka from "polka";
|
||||
import sirv from "sirv";
|
||||
import path from "path";
|
||||
import { dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const template = path.join(__dirname, "..", "gradio", "templates", "frontend");
|
||||
|
||||
export default async function global_setup() {
|
||||
const serve = sirv(template);
|
||||
const app = polka()
|
||||
.use(serve)
|
||||
.listen("3000", () => {
|
||||
console.log(`> Running on localhost: 3000`);
|
||||
});
|
||||
|
||||
return () => {
|
||||
app.server.close();
|
||||
};
|
||||
}
|
7
ui/playwright.config.js
Normal file
7
ui/playwright.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
export default {
|
||||
use: {
|
||||
screenshot: "only-on-failure",
|
||||
trace: "retain-on-failure"
|
||||
},
|
||||
globalSetup: "./playwright-setup.js"
|
||||
};
|
2112
ui/pnpm-lock.yaml
generated
2112
ui/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user