Skip to content

[WC-3363] Fileuploader: dismiss action for validation errors#2198

Open
yordan-st wants to merge 3 commits into
mainfrom
fix/WC-3363_fileuploader-close-warning
Open

[WC-3363] Fileuploader: dismiss action for validation errors#2198
yordan-st wants to merge 3 commits into
mainfrom
fix/WC-3363_fileuploader-close-warning

Conversation

@yordan-st
Copy link
Copy Markdown
Contributor

Pull request type

Bug fix (non-breaking change which fixes an issue)


Description

  • Fixed an issue where the "Invalid file format" validation error persisted indefinitely with no way to close it
  • Validation errors now auto-clear when a valid file uploads successfully, and each error entry has a dismiss button for manual removal

What should be covered while testing?

  • Configure File Uploader with restricted formats (e.g., only .pdf), upload a disallowed file (e.g., .xlsx), confirm "Invalid file format" error appears
  • While error is visible, upload an allowed file — verify the error disappears automatically after successful upload
  • Upload a disallowed file again, click the dismiss (X) button on the error entry — verify it's removed
  • Upload multiple disallowed files, then drop a new batch (mix of valid + invalid) — verify old errors clear and only new rejections show

@yordan-st yordan-st requested a review from a team as a code owner May 1, 2026 14:33
Comment thread packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts Outdated
Comment thread packages/pluggableWidgets/file-uploader-web/CHANGELOG.md Outdated
@github-actions
Copy link
Copy Markdown

AI Code Review

🔶 Changes requested — one or more medium-severity items must be addressed


What was reviewed

File Change
packages/pluggableWidgets/file-uploader-web/CHANGELOG.md Unreleased ### Fixed entry added
packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx New DismissActionsBar component; routes validationError status to it
packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts New dismiss() action; calls dismissValidationErrors() on upload success
packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts New dismissValidationErrors() and dismissFile() actions
packages/pluggableWidgets/file-uploader-web/src/ui/FileUploader.scss &.invalid opacity scoped to preview/main elements; entry-details-actions no longer hidden

Skipped (out of scope): dist/, pnpm-lock.yaml


Findings

🔶 Medium — No tests for new dismiss functionality

File: packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts lines 147–153
Problem: dismissValidationErrors(), dismissFile(), FileStore.dismiss(), and the DismissActionsBar component are all untested. The existing test suite covers only utility helpers (parseAllowedFormats, fileSize, DatasourceUpdateProcessor, ObjectCreationHelper) — there are no store or component tests at all for the main widget logic. This is an opportunity to establish coverage for the new behaviour.
Fix: Add unit tests covering at minimum:

// FileUploaderStore
it("dismissValidationErrors removes only validationError entries", () => {
    store.files = [
        FileStore.newFileWithError(mockFile, "bad format", store),
        FileStore.existingFile(mockItem, store),
    ];
    store.dismissValidationErrors();
    expect(store.files).toHaveLength(1);
    expect(store.files[0].fileStatus).toBe("existingFile");
});

it("dismissFile removes the specific file store", () => {
    const errStore = FileStore.newFileWithError(mockFile, "bad format", store);
    store.files = [errStore];
    store.dismissFile(errStore);
    expect(store.files).toHaveLength(0);
});

// DismissActionsBar — RTL
it("renders dismiss button for validationError status", () => {
    const fileStore = FileStore.newFileWithError(mockFile, "bad format", rootStore);
    render(<ActionsBar store={fileStore} />);
    expect(screen.getByRole("button", { name: /remove/i })).toBeInTheDocument();
});

🔶 Medium — Dismiss button reuses removeButtonTextMessage translation key

File: packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx line 88
Problem: DismissActionsBar uses translations.get("removeButtonTextMessage") — the same key as the remove-uploaded-file button in DefaultActionsBar. These are semantically different actions: one permanently removes a persisted Mendix object; the other just clears a client-side validation error from the UI. If the app author has set removeButtonTextMessage to something like "Delete file", the dismiss button on a validation-error row will read "Delete file", which is misleading and potentially alarming.
Fix: Introduce a dedicated translation key (e.g. dismissButtonTextMessage) in the XML and use it here:

title={translations.get("dismissButtonTextMessage")}

This requires a small addition to FileUploader.xml and FileUploaderProps.d.ts for the new text template property.


⚠️ Low — isDisabled={false} is unnecessary noise

File: packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx line 90
Note: isDisabled={false} is hardcoded. If ActionButton already defaults this prop to false, remove it to keep the JSX consistent with how DefaultActionsBar passes it dynamically. If the prop has no default and this is intentional, a brief comment would help.


Positives

  • Both new store methods (dismissValidationErrors, dismissFile) are correctly registered in makeObservable with action — no untracked mutation risk.
  • dismissValidationErrors() is called in two of the right places: at the start of processDrop (clears stale errors on a new drop) and inside the upload-success runInAction block in FileStore.upload() — matches the described behaviour exactly.
  • FileStore.dismiss() delegates to the root store, preserving the single-source-of-truth pattern used consistently throughout the codebase.
  • SCSS fix correctly scopes the opacity reduction to .entry-details-preview and .entry-details-main rather than the whole row, and removes the display: none that was hiding the actions area — a clean, targeted change.
  • CHANGELOG entry is present and follows Keep a Changelog format under [Unreleased].

@github-actions
Copy link
Copy Markdown

AI Code Review

⚠️ Approved with suggestions — low-severity items only, safe to merge


What was reviewed

File Change
src/components/ActionsBar.tsx Added DismissActionsBar branch for validationError files
src/stores/FileStore.ts Added dismiss() action; calls dismissValidationErrors() on successful upload
src/stores/FileUploaderStore.ts Added dismissValidationErrors() and dismissFile() actions; clears old errors on new drop
src/ui/FileUploader.scss Scoped opacity to sub-elements instead of whole entry; removed display:none on actions
src/components/__tests__/DismissActionsBar.spec.tsx New unit tests for dismiss button rendering and click
src/stores/__tests__/FileStore.spec.ts New unit test for dismiss() delegation
src/stores/__tests__/FileUploaderStore.spec.ts New unit tests for dismissValidationErrors() and dismissFile()
CHANGELOG.md Keep a Changelog entry added under [Unreleased]

Skipped (out of scope): dist/, pnpm-lock.yaml


Findings

⚠️ Low — Stale validation errors not cleared on too-many-files drop

File: src/stores/FileUploaderStore.ts line 164–169
Note: The too-many-files rejection branch returns early before dismissValidationErrors() is called (line 171). This means any pre-existing validationError entries remain visible even though the user dropped a new batch. The PR description says "upload multiple disallowed files, then drop a new batch — verify old errors clear and only new rejections show", but that path skips the clear when the rejection reason is too-many-files.

Consider moving dismissValidationErrors() above the early return:

this.dismissValidationErrors();  // clear before any early return

if (fileRejections.length && fileRejections[0].errors[0].code === "too-many-files") {
    this.setMessage(...);
    return;
}

⚠️ Low — Icon-only dismiss button has no aria-label

File: src/components/ActionsBar.tsx line 86–91
Note: ActionButton exposes only title (HTML tooltip) to provide the button label. The title attribute is not a reliable accessible name per WCAG 2.2 — assistive technologies often ignore it on interactive elements. The existing download/remove buttons share this pattern (pre-existing), but the new dismiss button introduces another instance. Adding aria-label to ActionButton or passing it through would bring the button into AA compliance.

// In ActionButton.tsx, add aria-label prop:
<button
    role="button"
    aria-label={title}   // <-- add this
    title={title}
    ...
>

⚠️ Low — Test comment inaccurate for done state

File: src/components/__tests__/DismissActionsBar.spec.tsx line 72
Note: The comment says "DefaultActionsBar renders 2 buttons (download + remove)". That's true, but the test forces fileStatus = "done" via a direct property write on a MobX observable after construction. This bypasses MobX reactivity and works only because ActionsBar reads fileStatus synchronously at render time. Consider using FileStore.newFileWithError + mutating to done via runInAction, or documenting why the direct write is acceptable, to avoid the pattern being copied incorrectly elsewhere.


Positives

  • dismissValidationErrors() and dismissFile() are both registered as MobX actions in makeObservable — correct.
  • Calling this._rootStore.dismissValidationErrors() inside the runInAction block of upload() keeps the mutation atomic with the status update.
  • The SCSS change correctly scopes opacity: 0.5 to entry-details-preview and entry-details-main only, leaving the actions area fully opaque so the dismiss button remains legible.
  • Three separate test files with focused describe blocks and a factory helper pattern — good test hygiene.
  • CHANGELOG entry is present and correctly formatted under [Unreleased].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants