Skip to content

Commit

Permalink
Add more ImageEditor js tests (#10446)
Browse files Browse the repository at this point in the history
* code

* Try

* add storybook story

* add changeset

* empty

* use local file

* add changeset

* fix

* Revert "fix"

This reverts commit 42750f3.

* fix

* add code

* add code

* code

* Fix

* Fix

* code

* fix

* delete cheetah

* code

* code

* empty

---------

Co-authored-by: Freddy Boulton <[email protected]>
Co-authored-by: gradio-pr-bot <[email protected]>
  • Loading branch information
3 people authored Jan 31, 2025
1 parent b7b96e6 commit 2cf449a
Show file tree
Hide file tree
Showing 13 changed files with 152 additions and 163 deletions.
7 changes: 7 additions & 0 deletions .changeset/whole-kids-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@gradio/imageeditor": patch
"@self/storybook": patch
"gradio": patch
---

feat:Add more ImageEditor js tests
7 changes: 5 additions & 2 deletions .github/workflows/storybook-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,18 @@ jobs:
uses: "gradio-app/gradio/.github/actions/install-all-deps@main"
with:
python_version: "3.10"
skip_build: "true"
skip_build: "false"
- name: build client
run: pnpm --filter @gradio/client build
- name: generate theme.css
run: |
. venv/bin/activate
python scripts/generate_theme.py --outfile js/storybook/theme.css
- run: pnpm exec playwright install chromium
- name: build storybook
run: pnpm build-storybook --quiet
run: |
. venv/bin/activate
pnpm build-storybook --quiet
- name: upload storybook
uses: actions/upload-artifact@v4
with:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ demo/unload_event_test/output_log.txt
demo/stream_video_out/output_*.ts
demo/stream_video_out/output_*.mp4
demo/stream_audio_out/*.mp3
#demo/image_editor_story/*.png

# Etc
.idea/*
Expand Down Expand Up @@ -93,6 +94,7 @@ client/js/test.js
storybook-static
build-storybook.log
js/storybook/theme.css
#js/storybook/public/output-image.png

# playwright
.config/playwright/.cache
2 changes: 1 addition & 1 deletion demo/image_editor_canvas_size/run.ipynb
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: image_editor_canvas_size"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Row():\n", " with gr.Column():\n", " image = gr.ImageEditor(label=\"Default Canvas. Not fixed\", elem_id=\"default\")\n", " with gr.Column():\n", " custom_canvas = gr.ImageEditor(label=\"Custom Canvas, not fixed\", canvas_size=(300, 300),\n", " elem_id=\"small\")\n", " with gr.Column():\n", " custom_canvas_fixed = gr.ImageEditor(label=\"Custom Canvas,fixed\", canvas_size=(500, 500), fixed_canvas=True,\n", " elem_id=\"fixed\")\n", " with gr.Column():\n", " width = gr.Number(label=\"Width\")\n", " height = gr.Number(label=\"Height\")\n", "\n", " image.change(lambda x: x[\"composite\"].shape, outputs=[height, width], inputs=image)\n", " custom_canvas.change(lambda x: x[\"composite\"].shape, outputs=[height, width], inputs=custom_canvas)\n", " custom_canvas_fixed.change(lambda x: x[\"composite\"].shape, outputs=[height, width], inputs=custom_canvas_fixed)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: image_editor_canvas_size"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Row():\n", " with gr.Column():\n", " image = gr.ImageEditor(label=\"Default Canvas. Not fixed\", elem_id=\"default\")\n", " get_image = gr.Button(\"Get Default\")\n", " with gr.Column():\n", " custom_canvas = gr.ImageEditor(label=\"Custom Canvas, not fixed\", canvas_size=(300, 300),\n", " elem_id=\"small\")\n", " get_small = gr.Button(\"Get Small\")\n", " with gr.Column():\n", " custom_canvas_fixed = gr.ImageEditor(label=\"Custom Canvas,fixed\", canvas_size=(500, 500), fixed_canvas=True,\n", " elem_id=\"fixed\")\n", " get_fixed = gr.Button(\"Get Fixed\")\n", " with gr.Column():\n", " width = gr.Number(label=\"Width\")\n", " height = gr.Number(label=\"Height\")\n", "\n", " get_image.click(lambda x: x[\"composite\"].shape, outputs=[height, width], inputs=image)\n", " get_small.click(lambda x: x[\"composite\"].shape, outputs=[height, width], inputs=custom_canvas)\n", " get_fixed.click(lambda x: x[\"composite\"].shape, outputs=[height, width], inputs=custom_canvas_fixed)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
9 changes: 6 additions & 3 deletions demo/image_editor_canvas_size/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@
with gr.Row():
with gr.Column():
image = gr.ImageEditor(label="Default Canvas. Not fixed", elem_id="default")
get_image = gr.Button("Get Default")
with gr.Column():
custom_canvas = gr.ImageEditor(label="Custom Canvas, not fixed", canvas_size=(300, 300),
elem_id="small")
get_small = gr.Button("Get Small")
with gr.Column():
custom_canvas_fixed = gr.ImageEditor(label="Custom Canvas,fixed", canvas_size=(500, 500), fixed_canvas=True,
elem_id="fixed")
get_fixed = gr.Button("Get Fixed")
with gr.Column():
width = gr.Number(label="Width")
height = gr.Number(label="Height")

image.change(lambda x: x["composite"].shape, outputs=[height, width], inputs=image)
custom_canvas.change(lambda x: x["composite"].shape, outputs=[height, width], inputs=custom_canvas)
custom_canvas_fixed.change(lambda x: x["composite"].shape, outputs=[height, width], inputs=custom_canvas_fixed)
get_image.click(lambda x: x["composite"].shape, outputs=[height, width], inputs=image)
get_small.click(lambda x: x["composite"].shape, outputs=[height, width], inputs=custom_canvas)
get_fixed.click(lambda x: x["composite"].shape, outputs=[height, width], inputs=custom_canvas_fixed)

if __name__ == "__main__":
demo.launch()
1 change: 1 addition & 0 deletions demo/image_editor_story/run.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: image_editor_story"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from pathlib import Path\n", "import subprocess\n", "\n", "\n", "def predict(im):\n", " path = str(Path(__file__).parent / \"output-image.png\")\n", " with open(path, \"wb\") as f:\n", " f.write(Path(im[\"composite\"]).read_bytes())\n", " print(\"Writing to \", path)\n", " return path\n", "\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Row():\n", " with gr.Column():\n", " im = gr.ImageEditor(interactive=True, elem_id=\"image_editor\",\n", " canvas_size=(800, 600),\n", " brush=gr.Brush(colors=[\"#ff0000\", \"#00ff00\", \"#0000ff\"]),\n", " type=\"filepath\",\n", " value={\"background\": \"https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg\",\n", " \"layers\": [],\n", " \"composite\": None})\n", " get = gr.Button(\"Get\")\n", "\n", " with gr.Column():\n", " output = gr.Image(value=None, elem_id=\"output\")\n", "\n", " get.click(predict, inputs=im, outputs=output)\n", "\n", "if __name__ == \"__main__\":\n", " app, _, _ = demo.launch(prevent_thread_lock=True)\n", " subprocess.call([\"node\", \"js/storybook/ie_automation.js\"])\n", " demo.close()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
34 changes: 34 additions & 0 deletions demo/image_editor_story/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import gradio as gr
from pathlib import Path
import subprocess


def predict(im):
path = str(Path(__file__).parent / "output-image.png")
with open(path, "wb") as f:
f.write(Path(im["composite"]).read_bytes())
print("Writing to ", path)
return path


with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
im = gr.ImageEditor(interactive=True, elem_id="image_editor",
canvas_size=(800, 600),
brush=gr.Brush(colors=["#ff0000", "#00ff00", "#0000ff"]),
type="filepath",
value={"background": "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
"layers": [],
"composite": None})
get = gr.Button("Get")

with gr.Column():
output = gr.Image(value=None, elem_id="output")

get.click(predict, inputs=im, outputs=output)

if __name__ == "__main__":
app, _, _ = demo.launch(prevent_thread_lock=True)
subprocess.call(["node", "js/storybook/ie_automation.js"])
demo.close()
178 changes: 27 additions & 151 deletions js/imageeditor/ImageEditor.stories.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -55,157 +55,6 @@
}}
/>

<!-- <Story
name="Image Editor Drawing Interactions"
args={{
value: {
path: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
url: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
orig_name: "cheetah.jpg"
},
type: "pil",
sources: ["upload", "webcam"],
interactive: "true",
brush: {
default_size: "auto",
colors: ["#ff0000", "#00ff00", "#0000ff"],
default_color: "#ff0000",
color_mode: "defaults"
},
eraser: {
default_size: "auto"
}
}}
play={async ({ canvasElement }) => {
const canvas = within(canvasElement);
const drawButton = canvas.getAllByLabelText("Draw button")[0];
userEvent.click(drawButton);
const drawCanvas = document.getElementsByTagName("canvas")[0];
if (!drawCanvas) {
throw new Error("Could not find canvas");
}
await new Promise((r) => setTimeout(r, 1000));
await userEvent.pointer({
keys: "[MouseLeft>]",
target: drawCanvas,
coords: { clientX: 300, clientY: 100 }
});
await userEvent.pointer({
keys: "[MouseLeft>]",
target: drawCanvas,
coords: { clientX: 300, clientY: 100 }
});
await userEvent.pointer({
keys: "[MouseLeft>]",
target: drawCanvas,
coords: { clientX: 300, clientY: 100 }
});
await userEvent.pointer({
target: drawCanvas,
coords: { clientX: 300, clientY: 300 }
});
await userEvent.pointer({
target: drawCanvas,
coords: { clientX: 300, clientY: 300 }
});
await userEvent.pointer({
target: drawCanvas,
coords: { clientX: 100, clientY: 100 }
});
await userEvent.click(canvas.getByLabelText("Draw button"));
var availableColors = document.querySelectorAll(
"button.color:not(.empty):not(.selected):not(.hidden)"
);
await userEvent.click(availableColors[0]);
await userEvent.keyboard("{Escape}");
await userEvent.pointer({
keys: "[MouseLeft>]",
target: drawCanvas,
coords: { clientX: 50, clientY: 50 }
});
await userEvent.pointer({
keys: "[MouseLeft>]",
target: drawCanvas,
coords: { clientX: 100, clientY: 100 }
});
await userEvent.pointer({
target: drawCanvas,
coords: { clientX: 100, clientY: 300 }
});
await userEvent.pointer({
target: drawCanvas,
coords: { clientX: 300, clientY: 300 }
});
await userEvent.pointer({
target: drawCanvas,
coords: { clientX: 100, clientY: 100 }
});
await userEvent.click(canvas.getByLabelText("Transform button"));
const bottomCropHandle =
document.getElementsByClassName("handle corner b")[0];
await userEvent.pointer({
keys: "[MouseLeft>]",
target: bottomCropHandle,
coords: { clientX: 1000, clientY: 200 }
});
await userEvent.pointer({
target: bottomCropHandle,
coords: { clientX: 500, clientY: 0 }
});
await userEvent.pointer({
keys: "[MouseLeft>]",
target: bottomCropHandle,
coords: { clientX: 500, clientY: 0 }
});
await userEvent.pointer({
keys: "[MouseLeft>]",
coords: { clientX: 500, clientY: 0 }
});
await userEvent.pointer({
target: drawCanvas,
coords: { clientX: 100, clientY: 300 }
});
userEvent.tripleClick(drawCanvas);
await new Promise((r) => setTimeout(r, 1000));
userEvent.click(canvas.getByLabelText("Show Layers"));
await new Promise((r) => setTimeout(r, 1000));
userEvent.click(canvas.getByLabelText("Add Layer"));
await userEvent.click(canvas.getByLabelText("Clear canvas"));
}}
/> -->

<Story
name="Image Editor Undo/Redo Interactions"
args={{
Expand Down Expand Up @@ -260,3 +109,30 @@
await userEvent.click(canvas.getByLabelText("Redo"));
}}
/>

<Story
name="Static Image Display"
args={{
value: {
composite: {
path: "",
url: "/output-image.png",
size: null,
orig_name: null,
mime_type: null,
is_stream: false,
meta: {
_type: "gradio.FileData"
}
},
layers: [],
background: null,
id: null
},
type: "pil",
interactive: false,
label: "Image Editor",
show_label: true,
canvas_size: [800, 600]
}}
/>
1 change: 1 addition & 0 deletions js/imageeditor/shared/layers/Layers.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
<li>
<button
class:selected_layer={$current_layer === layer}
aria-label={`layer-${i + 1}`}
on:click={() =>
($current_layer = LayerManager.change_active_layer(i))}
>Layer {i + 1}</button
Expand Down
18 changes: 14 additions & 4 deletions js/spa/test/image_editor_canvas_size.spec.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,56 @@
import { test, expect } from "@self/tootils";

test.skip("Image Editor canvas matches background image size if fixed_canvas=False", async ({
test("Image Editor canvas matches background image size if fixed_canvas=False", async ({
page
}) => {
await page.locator("#default >> .upload-container > button").click();
const uploader = page.locator("#default >> input[type=file]");
await uploader.setInputFiles(["./test/files/bike.jpeg"]);
await page.waitForTimeout(500);
await page.getByRole("button", { name: "Get Default" }).click();

await expect(page.getByLabel("Width")).toHaveValue("1024");
await expect(page.getByLabel("Height")).toHaveValue("769");
});

test.skip("Image Editor 300 x 300 canvas resizes to match uploaded image", async ({
test("Image Editor 300 x 300 canvas resizes to match uploaded image", async ({
page
}) => {
await page.locator("#small >> .upload-container > button").click();
const uploader = page.locator("#small >> input[type=file]");
await uploader.setInputFiles(["./test/files/bike.jpeg"]);
await page.waitForTimeout(500);
await page.getByRole("button", { name: "Get Small" }).click();

await expect(page.getByLabel("Width")).toHaveValue("1024");
await expect(page.getByLabel("Height")).toHaveValue("769");
});

test.skip("Image Editor 300 x 300 canvas maintains size while being drawn upon", async ({
test("Image Editor 300 x 300 canvas maintains size while being drawn upon", async ({
page
}) => {
await page.locator("#small").getByLabel("Draw button").click();
await page.locator("#small canvas").click({ position: { x: 15, y: 18 } });
await page.waitForTimeout(500);
await page.getByRole("button", { name: "Get Small" }).click();
await expect(page.getByLabel("Width")).toHaveValue("300");
await expect(page.getByLabel("Height")).toHaveValue("300");

await page.locator("#small canvas").click({ position: { x: 10, y: 12 } });
await page.waitForTimeout(500);
await page.getByRole("button", { name: "Get Small" }).click();
await expect(page.getByLabel("Width")).toHaveValue("300");
await expect(page.getByLabel("Height")).toHaveValue("300");
});

test.skip("Image Editor reshapes image to fit fixed 500 x 500 canvas", async ({
test("Image Editor reshapes image to fit fixed 500 x 500 canvas", async ({
page
}) => {
await page.locator("#small >> .upload-container > button").click();
const uploader = page.locator("#fixed >> input[type=file]");
await uploader.setInputFiles(["./test/files/bike.jpeg"]);
await page.waitForTimeout(500);
await page.getByRole("button", { name: "Get Fixed" }).click();

await expect(page.getByLabel("Width")).toHaveValue("500");
await expect(page.getByLabel("Height")).toHaveValue("500");
Expand Down
2 changes: 1 addition & 1 deletion js/spa/test/image_editor_events.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect, drag_and_drop_file } from "@self/tootils";
import { test, expect } from "@self/tootils";

test("upload events work as expected", async ({ page }) => {
await page.getByLabel("Upload button").first().click();
Expand Down
Loading

0 comments on commit 2cf449a

Please sign in to comment.