Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"use client";

import { FrontendEngine, IFrontendEngineData, IFrontendEngineRef } from "@lifesg/web-frontend-engine";
import { useRef } from "react";

const FIELD_ID = "field";

const SCHEMA: IFrontendEngineData = {
sections: {
section: {
uiType: "section",
children: {
[FIELD_ID]: {
uiType: "file-upload",
label: "Upload documents",
"data-testid": "file-upload",
uploadOnAddingFile: {
type: "base64",
url: "/api/upload",
},
},
},
},
},
};

export default function FileUploadCustomErrorPage() {
const formRef = useRef<IFrontendEngineRef | null>(null);

const handleSetCustomErrors = () => {
const values = formRef.current?.getValues();
const files = (values?.[FIELD_ID] ?? []) as { fileId: string }[];

formRef.current?.setErrors({
[FIELD_ID]: {
message: "Custom error message <strong>with bold text</strong>",
...(files.length > 0 && {
fileErrors: Object.fromEntries(
files.map((file, index) => [file.fileId, `Custom error message per file (${index})`])
),
}),
},
});
};

return (
<>
<FrontendEngine ref={formRef} data={SCHEMA} />
<button data-testid="set-custom-errors" onClick={handleSetCustomErrors} style={{ marginTop: "2rem" }}>
Set custom errors
</button>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { FrontendEngine, IFrontendEngineData } from "@lifesg/web-frontend-engine";

const SCHEMA: IFrontendEngineData = {
sections: {
section: {
uiType: "section",
children: {
"default-field": {
uiType: "file-upload",
label: "Default",
"data-testid": "file-upload-default",
uploadOnAddingFile: {
type: "base64",
url: "/api/upload",
},
},
},
},
},
};

export default function FileUploadFormStatesPage() {
return <FrontendEngine data={SCHEMA} />;
}
38 changes: 38 additions & 0 deletions e2e/nextjs-app/app/components/fields/file-upload/thumbnail.e2e.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client";

import { FrontendEngine, IFrontendEngineData } from "@lifesg/web-frontend-engine";

const SCHEMA: IFrontendEngineData = {
sections: {
section: {
uiType: "section",
children: {
field: {
uiType: "file-upload",
label: "Upload documents",
"data-testid": "file-upload",
uploadOnAddingFile: {
type: "base64",
url: "/api/upload",
},
},
},
},
},
defaultValues: {
field: [
{
fileId: "pdf-1",
fileName: "document.pdf",
uploadResponse: {
fileSize: 51200,
mimeType: "application/pdf",
},
},
],
},
};

export default function FileUploadThumbnailPage() {
return <FrontendEngine data={SCHEMA} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { FrontendEngine, IFrontendEngineData } from "@lifesg/web-frontend-engine";

const SCHEMA: IFrontendEngineData = {
sections: {
section: {
uiType: "section",
children: {
field: {
uiType: "file-upload",
label: "Upload documents",
"data-testid": "file-upload",
uploadOnAddingFile: {
type: "base64",
url: "/api/upload",
},
},
},
},
},
};

export default function FileUploadInteractionsPage() {
return <FrontendEngine data={SCHEMA} />;
}
25 changes: 25 additions & 0 deletions e2e/nextjs-app/app/components/fields/file-upload/warning.e2e.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use client";

import { IFrontendEngineData } from "@lifesg/web-frontend-engine";
import { createWarningPage } from "../../common";

const SCHEMA: IFrontendEngineData = {
sections: {
section: {
uiType: "section",
children: {
primary: {
uiType: "file-upload",
label: "Upload documents",
"data-testid": "file-upload",
uploadOnAddingFile: {
type: "base64",
url: "/api/upload",
},
},
},
},
},
};

export default createWarningPage({ schema: SCHEMA });
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Comment thread
qroll marked this conversation as resolved.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
121 changes: 121 additions & 0 deletions e2e/tests/components/fields/file-upload/file-upload.e2e.spec.ts
Comment thread
qroll marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { Page } from "@playwright/test";
import { createStoryTest, expect, test } from "../../../utils/fixtures";
import { createSolidColorPng } from "../../../utils/image-fixtures";

// =============================================================================
// HELPERS
// =============================================================================
const SAMPLE_PNG_PAYLOAD = {
name: "sample.png",
mimeType: "image/png",
buffer: createSolidColorPng(8, 8, 220, 220, 220),
};

const mockUploadAPI = (page: Page) =>
page.route("**/api/upload", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ success: true }),
})
);

// =============================================================================
// TEST FACTORIES
// =============================================================================
const createFileUploadTest = (story: string) =>
createStoryTest({
component: "fields/file-upload",
story,
createLocators: (page) => ({
dropzone: page.getByTestId("file-upload"),
uploadButton: page.getByRole("button", { name: "Upload files" }),
fileItem: (name: string) => page.getByText(name, { exact: true }),
setCustomErrorsButton: page.getByTestId("set-custom-errors"),
}),
});

const uploadInteractionsTest = createFileUploadTest("upload-interactions");
const formStatesTest = createFileUploadTest("form-states");
const thumbnailTest = createFileUploadTest("thumbnail");
const warningTest = createFileUploadTest("warning");
const customErrorTest = createFileUploadTest("custom-error");

// =============================================================================
// TESTS
// =============================================================================
test.describe("FileUpload", () => {
uploadInteractionsTest.describe(() => {
uploadInteractionsTest("Upload interactions", async ({ story }) => {
await mockUploadAPI(story.page);
await story.goto();

await test.step("Upload through button", async () => {
const fileChooserPromise = story.page.waitForEvent("filechooser");
await story.locators.uploadButton.click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(SAMPLE_PNG_PAYLOAD);

await expect(story.locators.fileItem(SAMPLE_PNG_PAYLOAD.name)).toBeVisible();
await story.snapshot("uploaded-through-button");
});
});
});

test.describe("Form states", () => {
formStatesTest.describe(() => {
formStatesTest("Visual", async ({ story }) => {
await story.goto();
await story.snapshot("mount");
});
});

formStatesTest.describe(() => {
formStatesTest("Mobile", async ({ story }) => {
await story.setViewport({ size: "mobile" });
await story.goto();
await story.snapshot("mount");
});
});
});

thumbnailTest.describe(() => {
thumbnailTest("Thumbnail", async ({ story }) => {
await story.goto();
await expect(story.locators.fileItem("document.pdf")).toBeVisible();
await story.waitForImageLoad();
await story.snapshot("mount");
});
});

warningTest.describe(() => {
warningTest("Warning", async ({ story }) => {
await story.goto();
await story.snapshot("mount");
});
});

test.describe("Custom errors", () => {
customErrorTest.describe(() => {
customErrorTest("Main field error", async ({ story }) => {
await story.goto();
await story.locators.setCustomErrorsButton.click();
await story.snapshot("main-field-error");
});

customErrorTest("Main field and per-file errors", async ({ story }) => {
await mockUploadAPI(story.page);
await story.goto();

const fileChooserPromise = story.page.waitForEvent("filechooser");
await story.locators.uploadButton.click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(SAMPLE_PNG_PAYLOAD);
await expect(story.locators.fileItem(SAMPLE_PNG_PAYLOAD.name)).toBeVisible();

await story.locators.setCustomErrorsButton.click();
await story.snapshot("main-and-per-file-errors");
});
});
});
});
Loading
Loading