Skip to content
Open
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
59 changes: 59 additions & 0 deletions src/__testing__/ShareModalWireContract.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,63 @@ describe('ShareModal resource-access wire contract', () => {
);
expect(resourceAccessMutator).not.toHaveBeenCalled();
});

it('surfaces host error text when visibility update fails with a string error', async () => {
const handleUpdateVisibility = jest
.fn()
.mockResolvedValue({ error: 'visibility rejected' });
const { props } = renderShareModal({
handleUpdateVisibility
} as Partial<ShareModalProps>);

fireEvent.mouseDown(document.getElementById('share-menu')!);
fireEvent.click(screen.getByRole('option', { name: 'Public' }));

await waitFor(() => expect(handleUpdateVisibility).toHaveBeenCalledWith('public'));
await waitFor(() =>
expect(props.notify).toHaveBeenCalledWith({
message: 'Failed to update visibility. visibility rejected',
event_type: 'error'
})
);
});

it('surfaces nested RTK error text when visibility update returns an error object', async () => {
const handleUpdateVisibility = jest
.fn()
.mockResolvedValue({ error: { error: 'permission denied' } });
const { props } = renderShareModal({
handleUpdateVisibility
} as Partial<ShareModalProps>);

fireEvent.mouseDown(document.getElementById('share-menu')!);
fireEvent.click(screen.getByRole('option', { name: 'Public' }));

await waitFor(() => expect(handleUpdateVisibility).toHaveBeenCalledWith('public'));
await waitFor(() =>
expect(props.notify).toHaveBeenCalledWith({
message: 'Failed to update visibility. permission denied',
event_type: 'error'
})
);
});

it('notifies success when visibility update succeeds', async () => {
const handleUpdateVisibility = jest.fn().mockResolvedValue({ error: '' });
const { props } = renderShareModal({
handleUpdateVisibility
} as Partial<ShareModalProps>);

fireEvent.mouseDown(document.getElementById('share-menu')!);
fireEvent.click(screen.getByRole('option', { name: 'Public' }));

await waitFor(() => expect(handleUpdateVisibility).toHaveBeenCalledWith('public'));
await waitFor(() =>
expect(props.notify).toHaveBeenCalledWith({
message: "Design 'My Design' is now public",
event_type: 'success'
})
);
});
});

53 changes: 46 additions & 7 deletions src/custom/ShareModal/ShareModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,30 @@ export type ResourceAccessArg = {
resourceAccessMappingPayload: ResourceAccessMappingPayload;
};

export type VisibilityUpdateError =
| string
| {
error?: string;
message?: string;
data?:
| {
message?: string;
[key: string]: unknown;
}
| string;
[key: string]: unknown;
};

export type VisibilityUpdateResponse =
| {
error?: VisibilityUpdateError;
data?: unknown;
[key: string]: unknown;
}
| void
| null
| undefined;

export interface ShareModalProps {
/** Function to close the share modal */
handleShareModalClose: () => void;
Expand All @@ -187,7 +211,7 @@ export interface ShareModalProps {
fetchAccessActors: () => Promise<User[]>;
/** Optional URL of the host application. Defaults to `null` if not provided */
hostURL?: string | null;
handleUpdateVisibility: (value: string) => Promise<{ error: string }>;
handleUpdateVisibility: (value: string) => Promise<VisibilityUpdateResponse>;
/**
* @deprecated Unused - never read. The component defines its own
* `handleShareWithNewUsers`, which shadows this prop and shares through
Expand Down Expand Up @@ -420,14 +444,28 @@ const ShareModal: React.FC<ShareModalProps> = ({

const handleDelete = async (actor: User) => handleRevokeAccess([actor]);

/* eslint-disable @typescript-eslint/no-explicit-any */
const notifyVisibilityChange = (res: any, value: any) => {
const notifyVisibilityChange = (res: VisibilityUpdateResponse, value: string) => {
const UPDATE_VISIBILITY_MSG = Array.isArray(selectedResource)
? `${startCase(dataName)}s (${selectedResource.length}) are now ${value}`
: `${startCase(dataName)} '${selectedResource.name}' is now ${value}`;
const FAILED_TO_UPDATE_VISIBILITY_MSG = `Failed to update visibility. ${res?.error?.error || ''}`;

if (!res.error) {
const err = res && typeof res === 'object' && 'error' in res ? res.error : undefined;
const detail =
typeof err === 'string'
? err
: typeof err === 'object' && err !== null
? typeof err.error === 'string'
? err.error
: typeof err.data === 'object' && err.data !== null && typeof err.data.message === 'string'
? err.data.message
: typeof err.message === 'string'
? err.message
: ''
: '';
const FAILED_TO_UPDATE_VISIBILITY_MSG = detail
? `Failed to update visibility. ${detail}`
: 'Failed to update visibility.';

if (!err) {
notify({
message: UPDATE_VISIBILITY_MSG,
event_type: 'success'
Expand All @@ -452,7 +490,8 @@ const ShareModal: React.FC<ShareModalProps> = ({
setUpdatingVisibility(true);
const res = await handleUpdateVisibility(value);
notifyVisibilityChange(res, value);
if (!res?.error) {
const err = res && typeof res === 'object' && 'error' in res ? res.error : undefined;
if (!err) {
setVisibility(value);
}
} finally {
Expand Down
7 changes: 6 additions & 1 deletion src/custom/ShareModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@ export {
type ResourceAccessActor,
type ResourceAccessMappingPayload
} from './resourceAccessPayload';
export type { ResourceAccessArg, ShareModalProps } from './ShareModal';
export type {
ResourceAccessArg,
ShareModalProps,
VisibilityUpdateError,
VisibilityUpdateResponse
} from './ShareModal';
export { ShareModal };
4 changes: 3 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ export {
type ResourceAccessActor,
type ResourceAccessArg,
type ResourceAccessMappingPayload,
type ShareModalProps
type ShareModalProps,
type VisibilityUpdateError,
type VisibilityUpdateResponse
} from './custom/ShareModal';

// Same nested-barrel dts-drop quirk as FeedbackButton above, and the whole
Expand Down
Loading