Skip to content

Commit

Permalink
Allow awaiting toast.promise (#462)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajmnz authored Sep 10, 2024
1 parent 2b99cd8 commit ee55353
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 19 deletions.
52 changes: 33 additions & 19 deletions src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,26 +124,30 @@ class Observer {
const p = promise instanceof Promise ? promise : promise();

let shouldDismiss = id !== undefined;
let result: ['resolve', ToastData] | ['reject', unknown];

p.then(async (response) => {
if (isHttpResponse(response) && !response.ok) {
shouldDismiss = false;
const message =
typeof data.error === 'function' ? await data.error(`HTTP error! status: ${response.status}`) : data.error;
const description =
typeof data.description === 'function'
? await data.description(`HTTP error! status: ${response.status}`)
: data.description;
this.create({ id, type: 'error', message, description });
} else if (data.success !== undefined) {
shouldDismiss = false;
const message = typeof data.success === 'function' ? await data.success(response) : data.success;
const description =
typeof data.description === 'function' ? await data.description(response) : data.description;
this.create({ id, type: 'success', message, description });
}
})
const originalPromise = p
.then(async (response) => {
result = ['resolve', response];
if (isHttpResponse(response) && !response.ok) {
shouldDismiss = false;
const message =
typeof data.error === 'function' ? await data.error(`HTTP error! status: ${response.status}`) : data.error;
const description =
typeof data.description === 'function'
? await data.description(`HTTP error! status: ${response.status}`)
: data.description;
this.create({ id, type: 'error', message, description });
} else if (data.success !== undefined) {
shouldDismiss = false;
const message = typeof data.success === 'function' ? await data.success(response) : data.success;
const description =
typeof data.description === 'function' ? await data.description(response) : data.description;
this.create({ id, type: 'success', message, description });
}
})
.catch(async (error) => {
result = ['reject', error];
if (data.error !== undefined) {
shouldDismiss = false;
const message = typeof data.error === 'function' ? await data.error(error) : data.error;
Expand All @@ -161,7 +165,17 @@ class Observer {
data.finally?.();
});

return id;
const unwrap = () =>
new Promise<ToastData>((resolve, reject) =>
originalPromise.then(() => (result[0] === 'reject' ? reject(result[1]) : resolve(result[1]))).catch(reject),
);

if (typeof id !== 'string' && typeof id !== 'number') {
// cannot Object.assign on undefined
return { unwrap };
} else {
return Object.assign(id, { unwrap });
}
};

custom = (jsx: (id: number | string) => React.ReactElement, data?: ExternalToast) => {
Expand Down
12 changes: 12 additions & 0 deletions test/tests/basic.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect, test } from '@playwright/test';
import { toast } from 'sonner';

test.beforeEach(async ({ page }) => {
await page.goto('/');
Expand Down Expand Up @@ -28,6 +29,17 @@ test.describe('Basic functionality', () => {
await expect(page.getByText('Loaded')).toHaveCount(1);
});

test('handle toast promise rejections', async ({ page }) => {
const rejectedPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Promise rejected')), 100));
try {
toast.promise(rejectedPromise, {});
} catch {
throw new Error('Promise should not have rejected without unwrap');
}

await expect(toast.promise(rejectedPromise, {}).unwrap()).rejects.toThrow('Promise rejected');
});

test('render custom jsx in toast', async ({ page }) => {
await page.getByTestId('custom').click();
await expect(page.getByText('jsx')).toHaveCount(1);
Expand Down

0 comments on commit ee55353

Please sign in to comment.