Skip to content

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

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

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

Conversation

@yordan-st
Copy link
Copy Markdown
Contributor

@yordan-st yordan-st commented May 1, 2026

Pull request type

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


Description

  • Fixed an issue where the "Invalid file format" validation error had no way to be dismissed
  • Each rejected file entry now has a dismiss (✕) button that removes the entry and clears the yellow dropzone warning
  • Yellow warning persists as long as any rejected files remain in the list; clears when the last one is dismissed
  • Dropping a new batch clears previous rejected entries and sets the warning only if the new batch also contains rejections

What should be covered while testing?

Single rejected file

  • Configure File Uploader with restricted formats (e.g. PDF only)
  • Drop one unsupported file — yellow warning appears, rejected entry visible
  • Click dismiss (✕) on the entry — entry removed, yellow warning disappears

Mixed batch (valid + invalid)

  • Drop a batch with one unsupported and one supported file
  • Yellow warning appears; both entries visible (supported file uploading, rejected file showing error)
  • Confirm yellow warning stays after the valid file finishes uploading
  • Click dismiss on the rejected entry — entry removed, yellow warning disappears

Multiple rejected files

  • Drop two unsupported files
  • Both rejected entries visible, yellow warning present
  • Dismiss one — yellow warning stays (one entry remains)
  • Dismiss the second — yellow warning disappears

New drop clears old rejections

  • Drop one unsupported file — rejected entry visible, yellow warning present
  • Drop one supported file — rejected entry from the previous drop disappears immediately and yellow warning clears immediately on drop (before upload completes)

@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

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

r0b1n
r0b1n previously approved these changes May 15, 2026
@yordan-st yordan-st force-pushed the fix/WC-3363_fileuploader-close-warning branch from 565106f to c5b3da1 Compare May 18, 2026 12:45
@yordan-st yordan-st force-pushed the fix/WC-3363_fileuploader-close-warning branch from c5b3da1 to d895e08 Compare May 21, 2026 12:22
@github-actions
Copy link
Copy Markdown

AI Code Review

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


What was reviewed

File Change
packages/pluggableWidgets/file-uploader-web/CHANGELOG.md Added [Unreleased] entry for the bug fix
packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx Added DismissActionsBar component; routes validationError status to it
packages/pluggableWidgets/file-uploader-web/src/components/__tests__/DismissActionsBar.spec.tsx New unit tests for dismiss button rendering and click behaviour
packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts Added dismiss() action; calls dismissValidationErrors() after successful upload
packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts Added dismissValidationErrors() and dismissFile() actions; both registered in makeObservable; processDrop now clears old validation errors before adding new ones
packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileStore.spec.ts New unit tests for FileStore.dismiss()
packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts New unit tests for dismissValidationErrors() and dismissFile()
packages/pluggableWidgets/file-uploader-web/src/ui/FileUploader.scss Scoped opacity to entry-details-preview/entry-details-main instead of entire invalid entry; removed display: none on actions

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

CI checks could not be retrieved automatically — verify passing state before merge.


Findings

⚠️ Low — Dismiss button reuses removeButtonTextMessage translation key

File: packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx line 88
Note: DismissActionsBar uses translations.get("removeButtonTextMessage") for the dismiss button title. This means the tooltip/accessible name is "Remove" (the same label as the permanent-file remove button). For validation-error entries there is no file object to remove — the action is dismissing an error — so the label is semantically misleading. Consider a dedicated dismissButtonTextMessage XML property (or at least re-using a more neutral existing key) so screen-reader users get an accurate description of the action.


⚠️ Low — makeStore() bypasses makeObservable, so MobX reactivity is untested

File: packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts lines 14-29
Note: The helper constructs a FileUploaderStore instance via Object.create(…) + Object.assign(…), skipping the constructor and therefore makeObservable. The tests only assert array contents (.files), so they still pass, but any test that needed to verify that a mutation is wrapped in a MobX action (strict-mode violation) or that a computed updates would be silently incorrect. This is fine for the narrow coverage here, but add a comment explaining the bypass so future contributors don't copy the pattern for observer/computed tests.


⚠️ Low — makeDoneFile mutates fileStatus directly outside a MobX action

File: packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts line 37
Note: (f as any).fileStatus = "done" bypasses MobX observable tracking. In tests that don't run with MobX strict mode this is harmless, but if strict mode is ever enabled these tests would throw. Prefer runInAction(() => { f.fileStatus = "done"; }).


Positives

  • Both new store methods (dismissValidationErrors, dismissFile) are correctly registered as action in makeObservable — no observable mutation escapes MobX tracking in production code.
  • Auto-clearing on successful upload (dismissValidationErrors() inside runInAction) is placed in the same transaction as the status update, keeping the UI update atomic.
  • SCSS change correctly scopes the opacity to only the preview/content cells, leaving the actions column fully opaque — a clean improvement over the previous display: none approach.
  • Test file for DismissActionsBar exercises the full render → click → spy chain via RTL, which is the correct pattern for this repo.
  • CHANGELOG entry is present and follows Keep a Changelog format.

@github-actions
Copy link
Copy Markdown

AI Code Review

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


What was reviewed

File Change
packages/pluggableWidgets/file-uploader-web/CHANGELOG.md Added [Unreleased] ### Fixed entry
packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx Added DismissActionsBar component; routes validationError files to it
packages/pluggableWidgets/file-uploader-web/src/components/Dropzone.tsx Wrapped isDragReject with isDragActive && to prevent stale warning state
packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts Added dismiss action (delegates to rootStore)
packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts Added dismissFile and dismissValidationErrors actions; updated processDrop
packages/pluggableWidgets/file-uploader-web/src/ui/FileUploader.scss Scoped opacity to preview/main sub-elements; removed display:none from actions in .invalid
packages/pluggableWidgets/file-uploader-web/src/components/__tests__/DismissActionsBar.spec.tsx New unit tests for DismissActionsBar
packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileStore.spec.ts New unit tests for FileStore.dismiss()
packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts New unit tests for dismissFile / dismissValidationErrors / processDrop

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

⚠️ CI checks could not be retrieved during this review — verify all checks are green before merging.


Findings

⚠️ Low — Missing afterEach mock cleanup in new test files

File: packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileStore.spec.ts line 42
File: packages/pluggableWidgets/file-uploader-web/src/components/__tests__/DismissActionsBar.spec.tsx line 52
Note: Neither file resets module-level mocks between tests. saveFile and fetchMxObject are set with mockResolvedValue inside a test body in FileStore.spec.ts and will bleed into any tests added later in the same suite. Add a cleanup to both files:

afterEach(() => {
    jest.clearAllMocks();
});

⚠️ Low — Manual DynamicValue objects instead of builders in DismissActionsBar.spec.tsx

File: packages/pluggableWidgets/file-uploader-web/src/components/__tests__/DismissActionsBar.spec.tsx line 18
Note: makeFakeProps constructs DynamicValue<string> fields by hand ({ value: v, status: "available" }). The repo standard is to use DynamicValueBuilder from @mendix/widget-plugin-test-utils, which correctly shapes the type and supports status variations:

import { dynamicValue } from "@mendix/widget-plugin-test-utils";

removeButtonTextMessage: dynamicValue("Remove"),
downloadButtonTextMessage: dynamicValue("Download"),
// etc.

Positives

  • dismiss, dismissFile, and dismissValidationErrors are all properly registered as action in their respective makeObservable calls — no stray mutations outside action context.
  • The isDragActive && isDragReject fix in Dropzone.tsx is a clean, minimal correction that prevents the reject-state indicator from persisting after the user stops dragging.
  • processDrop correctly calls dismissValidationErrors() before processing the new drop, so stale rejected entries are cleared atomically on each new batch.
  • Test coverage is thorough: per-file dismiss, multi-file dismiss, last-validation-error clears the message, no-op when file not in list, and drop-with-rejections vs drop-without-rejections are all covered.
  • CHANGELOG entry is present in [Unreleased] with a clear user-facing description.

Comment on lines +154 to +156
if (!this.files.some(f => f.fileStatus === "validationError")) {
this.setMessage();
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ugly, dismissFile shouldn't do it. This should be a calculated field. Next PR kills it fortunately.

@github-actions
Copy link
Copy Markdown

AI Code Review

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


What was reviewed

File Change
packages/pluggableWidgets/file-uploader-web/CHANGELOG.md Added unreleased entry for the dismiss fix
packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx Added DismissActionsBar component; early-return for validationError status
packages/pluggableWidgets/file-uploader-web/src/components/Dropzone.tsx Guard isDragReject with isDragActive to suppress false-positive warning state
packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts Added dismiss() action that delegates to root store
packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts Added dismissFile() and dismissValidationErrors() methods; wired up in processDrop
packages/pluggableWidgets/file-uploader-web/src/ui/FileUploader.scss Moved opacity from entry-level to child elements; removed display:none on .entry-details-actions
packages/pluggableWidgets/file-uploader-web/src/components/__tests__/DismissActionsBar.spec.tsx New unit tests for DismissActionsBar
packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileStore.spec.ts New unit test for FileStore.dismiss()
packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts New unit tests for dismissFile and processDrop error-message behaviour

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


Findings

⚠️ Low — dismissFile does not clear message when a non-validation-error file is dismissed alongside the last rejected file

File: packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts line 151–156
Problem: dismissFile only calls setMessage() when no remaining files have validationError status. This is correct for the happy path, but if someone were to extend the method to also dismiss non-error files (e.g. a future remove path), the logic would silently skip clearing the message. More concretely right now: if the last validationError file is dismissed and a done file with a lingering warning (set before the done transition) is still present, the message will correctly clear — but the asymmetry between dismissValidationErrors (always clears) and dismissFile (only clears when no validation errors remain) can be surprising and isn't covered by a test.

The existing test suite covers the "no-op when file not in list" case but doesn't assert the errorMessage is unchanged when a done file remains alongside the dismissed error file — a subtle case.

Fix: Add a test asserting that errorMessage is preserved when dismissing a validationError file while another validationError file still exists (already covered), and add a complementary case verifying errorMessage is cleared when the only remaining files after dismissal are non-error files:

it("clears errorMessage when dismissed file was the last validationError but done files remain", () => {
    const store = makeStore();
    store.errorMessage = "Some files may not be uploadable.";
    const errorFile = makeValidationErrorFile(store);
    const doneFile = makeDoneFile(store);
    store.files = [errorFile, doneFile];

    store.dismissFile(errorFile);

    expect(store.errorMessage).toBeUndefined();
    expect(store.files).toHaveLength(1);
});

⚠️ Low — makeStore() bypasses makeObservable, so MobX reactivity is not tested

File: packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts line 14–29
Problem: The store is created via Object.create(FileUploaderStore.prototype) + Object.assign, which skips the constructor and therefore skips makeObservable. Mutating store.files directly in tests works today because the tests only assert final values, not reactive updates. If any future test needs to verify that a MobX reaction fires, this helper will silently fail. The errorMessage field also won't be observable in this setup, so the store.errorMessage = "..." assignments are plain property sets.

This is a pre-existing test-architecture issue that this PR introduces — not a blocker, but worth noting.

Fix: Consider constructing a minimal real store using actual FileUploaderContainerProps-like input and TranslationsStore, similar to how DismissActionsBar.spec.tsx does it with makeFakeProps(). This makes the store fully wired with MobX.


⚠️ Low — actions prop is not forwarded into DismissActionsBar

File: packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx line 13–16
Problem: The early-return on validationError means custom actions (from Studio Pro) are completely ignored for rejected files, with no comment explaining this is intentional. If widget consumers configure custom buttons, they will silently disappear for validation-errored entries.

Fix: If this is intentional design (only the dismiss button for rejected files, regardless of custom buttons), add a brief inline comment so future maintainers don't accidentally break this assumption:

// Validation-errored files always show only the dismiss button; custom actions are not applicable.
if (store.fileStatus === "validationError") {
    return <DismissActionsBar store={store} />;
}

Positives

  • The isDragActive && isDragReject fix in Dropzone.tsx is a clean, targeted correction — react-dropzone reports isDragReject even when not dragging (after a rejected drop), so gating on isDragActive prevents the yellow warning from rendering on hover after a prior rejection.
  • dismissValidationErrors() is correctly called before setting a new message in processDrop, ensuring stale error entries from the previous drop batch are always cleared atomically.
  • Both dismiss in FileStore and dismissFile in FileUploaderStore are properly registered in makeObservable as action, keeping MobX state mutations correctly batched.
  • Test coverage is thorough: edge cases (no-op dismiss, partial dismiss, last-file-dismissed) are all exercised.
  • CHANGELOG entry is present and well-worded.

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