mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 17:56:46 +00:00
Improve picture select crop error handling (#23352)
* Improve crop editor error handling * Add tests for data/image_upload * Fix tests imports
This commit is contained in:
parent
e58bef7795
commit
0a28bbdd72
@ -10,6 +10,7 @@ import {
|
|||||||
getIdFromUrl,
|
getIdFromUrl,
|
||||||
createImage,
|
createImage,
|
||||||
generateImageThumbnailUrl,
|
generateImageThumbnailUrl,
|
||||||
|
getImageData,
|
||||||
} from "../data/image_upload";
|
} from "../data/image_upload";
|
||||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||||
import type { CropOptions } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
import type { CropOptions } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
||||||
@ -197,8 +198,7 @@ export class HaPictureUpload extends LitElement {
|
|||||||
const url = generateImageThumbnailUrl(mediaId, undefined, true);
|
const url = generateImageThumbnailUrl(mediaId, undefined, true);
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
data = await getImageData(url);
|
||||||
data = await response.blob();
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
text: err.toString(),
|
text: err.toString(),
|
||||||
|
@ -80,3 +80,17 @@ export const deleteImage = (hass: HomeAssistant, id: string) =>
|
|||||||
type: "image/delete",
|
type: "image/delete",
|
||||||
image_id: id,
|
image_id: id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getImageData = async (url: string) => {
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch image: ${
|
||||||
|
response.statusText ? response.statusText : response.status
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.blob();
|
||||||
|
};
|
||||||
|
150
test/data/image_upload.test.ts
Normal file
150
test/data/image_upload.test.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||||
|
import {
|
||||||
|
getIdFromUrl,
|
||||||
|
generateImageThumbnailUrl,
|
||||||
|
fetchImages,
|
||||||
|
createImage,
|
||||||
|
updateImage,
|
||||||
|
deleteImage,
|
||||||
|
getImageData,
|
||||||
|
URL_PREFIX,
|
||||||
|
MEDIA_PREFIX,
|
||||||
|
} from "../../src/data/image_upload";
|
||||||
|
import type { HomeAssistant } from "../../src/types";
|
||||||
|
|
||||||
|
describe("image_upload", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getIdFromUrl", () => {
|
||||||
|
it("should extract id from URL_PREFIX", () => {
|
||||||
|
const url = `${URL_PREFIX}12345/some/path`;
|
||||||
|
const id = getIdFromUrl(url);
|
||||||
|
expect(id).toBe("12345");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should extract id from MEDIA_PREFIX", () => {
|
||||||
|
const url = `${MEDIA_PREFIX}/12345`;
|
||||||
|
const id = getIdFromUrl(url);
|
||||||
|
expect(id).toBe("12345");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined for invalid url", () => {
|
||||||
|
const url = "invalid_url";
|
||||||
|
const id = getIdFromUrl(url);
|
||||||
|
expect(id).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("generateImageThumbnailUrl", () => {
|
||||||
|
it("should generate thumbnail URL with size", () => {
|
||||||
|
const url = generateImageThumbnailUrl("12345", 100);
|
||||||
|
expect(url).toBe("/api/image/serve/12345/100x100");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should generate original image URL", () => {
|
||||||
|
const url = generateImageThumbnailUrl("12345", undefined, true);
|
||||||
|
expect(url).toBe("/api/image/serve/12345/original");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error if size is not provided and original is false", () => {
|
||||||
|
expect(() => generateImageThumbnailUrl("12345")).toThrow(
|
||||||
|
"Size must be provided if original is false"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetchImages", () => {
|
||||||
|
it("should fetch images", async () => {
|
||||||
|
const hass = {
|
||||||
|
callWS: vi.fn().mockResolvedValue([]),
|
||||||
|
} as unknown as HomeAssistant;
|
||||||
|
const images = await fetchImages(hass);
|
||||||
|
expect(hass.callWS).toHaveBeenCalledWith({ type: "image/list" });
|
||||||
|
expect(images).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("createImage", () => {
|
||||||
|
it("should create an image", async () => {
|
||||||
|
const file = new File([""], "image.png", { type: "image/png" });
|
||||||
|
const hass = {
|
||||||
|
fetchWithAuth: vi.fn().mockResolvedValue({
|
||||||
|
status: 200,
|
||||||
|
json: vi.fn().mockResolvedValue({ id: "12345" }),
|
||||||
|
}),
|
||||||
|
} as unknown as HomeAssistant;
|
||||||
|
const image = await createImage(hass, file);
|
||||||
|
expect(hass.fetchWithAuth).toHaveBeenCalled();
|
||||||
|
expect(image).toEqual({ id: "12345" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error if image is too large", async () => {
|
||||||
|
const file = new File([""], "image.png", { type: "image/png" });
|
||||||
|
const hass = {
|
||||||
|
fetchWithAuth: vi.fn().mockResolvedValue({ status: 413 }),
|
||||||
|
} as unknown as HomeAssistant;
|
||||||
|
await expect(createImage(hass, file)).rejects.toThrow(
|
||||||
|
"Uploaded image is too large (image.png)"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error if fetch fails", async () => {
|
||||||
|
const file = new File([""], "image.png", { type: "image/png" });
|
||||||
|
const hass = {
|
||||||
|
fetchWithAuth: vi.fn().mockResolvedValue({ status: 500 }),
|
||||||
|
} as unknown as HomeAssistant;
|
||||||
|
await expect(createImage(hass, file)).rejects.toThrow("Unknown error");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("updateImage", () => {
|
||||||
|
it("should update an image", async () => {
|
||||||
|
const hass = {
|
||||||
|
callWS: vi.fn().mockResolvedValue({}),
|
||||||
|
} as unknown as HomeAssistant;
|
||||||
|
const updates = { name: "new name" };
|
||||||
|
await updateImage(hass, "12345", updates);
|
||||||
|
expect(hass.callWS).toHaveBeenCalledWith({
|
||||||
|
type: "image/update",
|
||||||
|
media_id: "12345",
|
||||||
|
...updates,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("deleteImage", () => {
|
||||||
|
it("should delete an image", async () => {
|
||||||
|
const hass = {
|
||||||
|
callWS: vi.fn().mockResolvedValue({}),
|
||||||
|
} as unknown as HomeAssistant;
|
||||||
|
await deleteImage(hass, "12345");
|
||||||
|
expect(hass.callWS).toHaveBeenCalledWith({
|
||||||
|
type: "image/delete",
|
||||||
|
image_id: "12345",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getImageData", () => {
|
||||||
|
it("should fetch image data", async () => {
|
||||||
|
global.fetch = vi.fn().mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
blob: vi.fn().mockResolvedValue(new Blob()),
|
||||||
|
});
|
||||||
|
const data = await getImageData("http://example.com/image.png");
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith("http://example.com/image.png");
|
||||||
|
expect(data).toBeInstanceOf(Blob);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error if fetch fails", async () => {
|
||||||
|
global.fetch = vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ ok: false, statusText: "Not Found" });
|
||||||
|
await expect(
|
||||||
|
getImageData("http://example.com/image.png")
|
||||||
|
).rejects.toThrow("Failed to fetch image: Not Found");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user