Slider browser unit tests (#4681)

* First commit

* Fix test

* move to .config

* indent

* Upgrade pnpm

* Lint + ts

* Add more tests

* Lint

* fix test

* Use spy
This commit is contained in:
Freddy Boulton 2023-06-27 10:28:27 -04:00 committed by GitHub
parent b7a52c410a
commit 340718e6c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 5449 additions and 2220 deletions

View File

@ -0,0 +1,93 @@
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import sveltePreprocess from "svelte-preprocess";
// @ts-ignore
import custom_media from "postcss-custom-media";
import global_data from "@csstools/postcss-global-data";
// @ts-ignore
import prefixer from "postcss-prefix-selector";
import { readFileSync } from "fs";
import { join } from "path";
import { fileURLToPath } from "url";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const version_path = join(__dirname, "..", "gradio", "version.txt");
const theme_token_path = join(
__dirname,
"..",
"js",
"theme",
"src",
"tokens.css"
);
const version = readFileSync(version_path, { encoding: "utf-8" })
.trim()
.replace(/\./g, "-");
//@ts-ignore
export default defineConfig(({ mode }) => {
const production =
mode === "production:cdn" ||
mode === "production:local" ||
mode === "production:website";
return {
server: {
port: 9876
},
build: {
sourcemap: false,
target: "esnext",
minify: production
},
define: {
BUILD_MODE: production ? JSON.stringify("prod") : JSON.stringify("dev"),
BACKEND_URL: production
? JSON.stringify("")
: JSON.stringify("http://localhost:7860/"),
GRADIO_VERSION: JSON.stringify(version)
},
css: {
postcss: {
plugins: [
prefixer({
prefix: `.gradio-container-${version}`,
// @ts-ignore
transform(prefix, selector, prefixedSelector, fileName) {
if (selector.indexOf("gradio-container") > -1) {
return prefix;
} else if (
selector.indexOf(":root") > -1 ||
selector.indexOf("dark") > -1 ||
fileName.indexOf(".svelte") > -1
) {
return selector;
}
return prefixedSelector;
}
}),
custom_media()
]
}
},
plugins: [
svelte({
inspector: true,
compilerOptions: {
dev: !production
},
hot: !process.env.VITEST && !production,
preprocess: sveltePreprocess({
postcss: {
plugins: [
global_data({ files: [theme_token_path] }),
custom_media()
]
}
})
})
]
};
});

View File

@ -0,0 +1,41 @@
import { defineConfig, devices } from "@playwright/experimental-ct-svelte";
import config from "./basevite.config";
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "../",
/* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */
snapshotDir: "./__snapshots__",
/* Maximum time one test can run for. */
timeout: 10 * 1000,
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
/* Port to use for Playwright component endpoint. */
ctPort: 3100,
ctViteConfig: config({ mode: "development" })
},
testMatch: "*.component.spec.ts",
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] }
}
]
});

View File

@ -12,7 +12,12 @@ const TEST_FILES_PATH = join(__dirname, "..", "js", "app", "test");
const ROOT = join(__dirname, "..");
const test_files = readdirSync(TEST_FILES_PATH)
.filter((f) => f.endsWith("spec.ts") && !f.endsWith(".skip.spec.ts"))
.filter(
(f) =>
f.endsWith("spec.ts") &&
!f.endsWith(".skip.spec.ts") &&
!f.endsWith(".component.spec.ts")
)
.map((f) => basename(f, ".spec.ts"));
export default async function global_setup() {

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Testing Page</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.ts"></script>
</body>
</html>

View File

@ -0,0 +1,2 @@
// Import styles, initialize component theme here.
// import '../src/common.css';

View File

@ -53,3 +53,7 @@ jobs:
run: |
. venv/bin/activate
pnpm test:browser
- name: run browser component tests
run: |
. venv/bin/activate
pnpm run test:ct

6
.gitignore vendored
View File

@ -29,6 +29,7 @@ tmp.zip
coverage.xml
test.txt
**/snapshots/**/*.png
playwright-report/
# Demos
demo/tmp.zip
@ -63,4 +64,7 @@ node_modules
public/build/
test-results
client/js/test.js
.config/test.py
.config/test.py
.config/playwright
!.config/playwright/index.html
!.config/playwright/index.ts

View File

@ -0,0 +1,132 @@
import { test, expect } from "@playwright/experimental-ct-svelte";
import type { Page, Locator } from "@playwright/test";
import Slider from "./Slider.svelte";
import { spy } from "tinyspy";
import type { LoadingStatus } from "../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"
};
//taken from: https://github.com/microsoft/playwright/issues/20032
async function changeSlider(
page: Page,
thumb: Locator,
slider: Locator,
targetPercentage: number
) {
const thumbBoundingBox = await thumb.boundingBox();
const sliderBoundingBox = await slider.boundingBox();
if (thumbBoundingBox === null || sliderBoundingBox === null) {
return; // NOTE it's probably better to throw an error here
}
// Start from the middle of the slider's thumb
const startPoint = {
x: thumbBoundingBox.x + thumbBoundingBox.width / 2,
y: thumbBoundingBox.y + thumbBoundingBox.height / 2
};
// Slide it to some endpoint determined by the target percentage
const endPoint = {
x: sliderBoundingBox.x + sliderBoundingBox.width * targetPercentage,
y: thumbBoundingBox.y + thumbBoundingBox.height / 2
};
await page.mouse.move(startPoint.x, startPoint.y);
await page.mouse.down();
await page.mouse.move(endPoint.x, endPoint.y);
await page.mouse.up();
}
test("Slider Default Value And Label rendered", async ({ mount }) => {
const component = await mount(Slider, {
props: {
value: 3,
minimum: 0,
maximum: 10,
label: "My Slider",
show_label: true,
step: 1,
mode: "dynamic",
loading_status: loading_status
}
});
await expect(component).toContainText("My Slider");
await expect(component.getByLabel("My Slider")).toHaveValue("3");
});
test("Slider respects show_label", async ({ mount, page }) => {
const _ = await mount(Slider, {
props: {
value: 3,
minimum: 0,
maximum: 10,
label: "My Slider",
show_label: false,
step: 1,
mode: "dynamic",
loading_status: loading_status
}
});
await expect(page.getByTestId("label")).toBeHidden();
});
test("Slider Maximum/Minimum values", async ({ mount, page }) => {
const component = await mount(Slider, {
props: {
value: 3,
minimum: 0,
maximum: 10,
label: "My Slider",
show_label: true,
step: 1,
mode: "dynamic",
loading_status: loading_status
}
});
const slider = component.getByLabel("My Slider");
await changeSlider(page, slider, slider, 1);
await expect(component.getByLabel("My Slider")).toHaveValue("10");
await changeSlider(page, slider, slider, 0);
await expect(component.getByLabel("My Slider")).toHaveValue("0");
});
test("Slider Change event", async ({ mount, page }) => {
let change = spy();
let release = spy();
const component = await mount(Slider, {
props: {
value: 3,
minimum: 0,
maximum: 10,
label: "My Slider",
show_label: true,
step: 1,
mode: "dynamic",
loading_status: loading_status
},
on: {
change: change,
release: release
}
});
const slider = page.getByLabel("Slider");
await changeSlider(page, slider, slider, 0.7);
await expect(component.getByLabel("My Slider")).toHaveValue("7");
// More than one change event and one release event.
await expect(change.callCount).toBeGreaterThan(1);
await expect(release.callCount).toEqual(1);
});

View File

@ -1,48 +0,0 @@
import { Page, Locator } from "@playwright/test";
import { test, expect } from "@gradio/tootils";
//taken from: https://github.com/microsoft/playwright/issues/20032
async function changeSlider(
page: Page,
thumb: Locator,
slider: Locator,
targetPercentage: number
) {
const thumbBoundingBox = await thumb.boundingBox();
const sliderBoundingBox = await slider.boundingBox();
if (thumbBoundingBox === null || sliderBoundingBox === null) {
return; // NOTE it's probably better to throw an error here
}
// Start from the middle of the slider's thumb
const startPoint = {
x: thumbBoundingBox.x + thumbBoundingBox.width / 2,
y: thumbBoundingBox.y + thumbBoundingBox.height / 2
};
// Slide it to some endpoint determined by the target percentage
const endPoint = {
x: sliderBoundingBox.x + sliderBoundingBox.width * targetPercentage,
y: thumbBoundingBox.y + thumbBoundingBox.height / 2
};
await page.mouse.move(startPoint.x, startPoint.y);
await page.mouse.down();
await page.mouse.move(endPoint.x, endPoint.y);
await page.mouse.up();
}
test("slider release", async ({ page }) => {
const slider = page.getByLabel("Slider");
await changeSlider(page, slider, slider, 0.7);
const value = page.getByLabel("On release");
const events = page.getByLabel("Number of events fired");
const val = await slider.inputValue();
expect(parseInt(val)).toBeCloseTo(70);
expect(parseInt(await value.inputValue())).toBeCloseTo(70);
expect(events).toHaveValue("1");
});

View File

@ -24,7 +24,8 @@
"test:browser:verbose": "GRADIO_TEST_VERBOSE=true pnpm test:browser",
"test:browser:debug": "pnpm --filter @gradio/app test:browser:debug",
"ci:publish": "pnpm publish --no-git-checks --access public -r",
"ci:version": "changeset version && pnpm i --lockfile-only"
"ci:version": "changeset version && pnpm i --lockfile-only",
"test:ct": "playwright test -c ./.config/playwright-ct.config.ts"
},
"type": "module",
"author": "",
@ -35,7 +36,7 @@
"@changesets/cli": "^2.26.1",
"@csstools/postcss-global-data": "^1.0.3",
"@gradio/tootils": "workspace:^0.0.1",
"@playwright/test": "^1.35.1",
"@playwright/experimental-ct-svelte": "^1.35.1",
"@sveltejs/vite-plugin-svelte": "^2.0.0",
"@tailwindcss/forms": "^0.5.0",
"@testing-library/dom": "^9.0.0",
@ -56,7 +57,6 @@
"msw": "^1.0.0",
"node-html-parser": "^6.0.0",
"npm-run-all": "^4.1.5",
"playwright": "^1.35.1",
"plotly.js-dist-min": "^2.10.1",
"polka": "^1.0.0-next.22",
"pollen-css": "^4.6.1",
@ -80,6 +80,7 @@
"vitest": "^0.32.0"
},
"devDependencies": {
"@playwright/test": "^1.35.1",
"@types/three": "^0.152.0"
},
"prettier": {

7308
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,10 @@
"compilerOptions": {
"moduleResolution": "node",
"module": "es2020",
"lib": ["es2020", "DOM"],
"lib": [
"es2020",
"DOM"
],
"target": "es2020",
/**
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
@ -24,7 +27,7 @@
"exclude": [
"**/dist/**/*",
"**/public/**/*",
"**/.config/*",
"**/.config/**/*",
"**/*.test.ts",
"**/dist",
".github/**/*",
@ -46,4 +49,4 @@
"**/*.svelte",
"client/js/**/*"
]
}
}