Skip to content

Commit

Permalink
Merge pull request #28464 from storybookjs/kasper/error-handling
Browse files Browse the repository at this point in the history
Core: Make sure StorybookError message shows up in browser console and interactions panel
  • Loading branch information
kasperpeulen authored Jul 7, 2024
2 parents 567aa02 + 13338a2 commit 3a88baa
Show file tree
Hide file tree
Showing 11 changed files with 639 additions and 751 deletions.
2 changes: 1 addition & 1 deletion code/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ module.exports = {
},
},
{
files: ['**/core-events/src/**/*'],
files: ['./core/src/preview-errors.ts'],
excludedFiles: ['**/*.test.*'],
rules: {
'local-rules/no-duplicated-error-codes': 'error',
Expand Down
44 changes: 23 additions & 21 deletions code/core/src/ERRORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ Second use the `StorybookError` class to define custom errors with specific code
```typescript
import { StorybookError } from './storybook-error';
export class YourCustomError extends StorybookError {
readonly category: Category; // The category to which the error belongs. Check the source in client-errors.ts or server-errors.ts for reference.
readonly code: number; // The numeric code for the error.

template(): string {
// A function that returns the error message.
constructor() {
super({
// The category to which the error belongs. Check the source in client-errors.ts or server-errors.ts for reference.
category: Category,
// The numeric code for the error.
code: number,
// The error message.
message: string,
});
}
}
```
Expand All @@ -42,7 +46,7 @@ export class YourCustomError extends StorybookError {
| ------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| category | `Category` | The category to which the error belongs. |
| code | `number` | The numeric code for the error. |
| template | `() => string` | Function that returns a properly written error message. |
| message | `string` | The error message. |
| data | `Object` | Optional. Data associated with the error. Used to provide additional information in the error message or to be passed to telemetry. |
| documentation | `boolean` or `string` | Optional. Should be set to `true` **if the error is documented on the Storybook website**. If defined as string, it should be a custom documentation link. |

Expand All @@ -51,28 +55,26 @@ export class YourCustomError extends StorybookError {
```typescript
// Define a custom error with a numeric code and a static error message template.
export class StorybookIndexGenerationError extends StorybookError {
category = Category.Generic;
code = 1;

template(): string {
return `Storybook failed when generating an index for your stories. Check the stories field in your main.js`;
constructor() {
super({
category: Category.Generic,
code: 1,
message: `Storybook failed when generating an index for your stories. Check the stories field in your main.js`,
});
}
}

// Define a custom error with a numeric code and a dynamic error message template based on properties from the constructor.
// Define a custom error with a numeric code and a dynamic error message based on properties from the constructor.
export class InvalidFileExtensionError extends StorybookError {
category = Category.Validation;
code = 2;
documentation = 'https://some-custom-documentation.com/validation-errors';

// extra properties are defined in the constructor via a data property, which is available in any class method
// always use this data Object notation!
constructor(public data: { extension: string }) {
super();
}

template(): string {
return `Invalid file extension found: ${this.data.extension}.`;
super({
category: Category.Validation,
code: 2,
documentation: 'https://some-custom-documentation.com/validation-errors',
message: `Invalid file extension found: ${data.extension}.`,
});
}
}

Expand Down
1 change: 0 additions & 1 deletion code/core/src/__tests/preview-errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ describe('UnknownFlowArgTypesError', () => {
const typeError = new UnknownArgTypesError({ type, language: 'Typescript' });
expect(typeError.message).toMatchInlineSnapshot(`
"There was a failure when generating detailed ArgTypes in Typescript for:
{
"name": "signature",
"raw": "SomeType['someProperty']"
Expand Down
24 changes: 11 additions & 13 deletions code/core/src/__tests/storybook-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { StorybookError } from '../storybook-error';

describe('StorybookError', () => {
class TestError extends StorybookError {
category = 'TEST_CATEGORY';

code = 123;

template() {
return 'This is a test error.';
constructor(documentation?: StorybookError['documentation']) {
super({
category: 'TEST_CATEGORY',
code: 123,
message: 'This is a test error.',
documentation,
});
}
}

Expand All @@ -24,16 +25,14 @@ describe('StorybookError', () => {
});

it('should generate the correct message with internal documentation link', () => {
const error = new TestError();
error.documentation = true;
const error = new TestError(true);
const expectedMessage =
'This is a test error.\n\nMore info: https://storybook.js.org/error/SB_TEST_CATEGORY_0123\n';
expect(error.message).toBe(expectedMessage);
});

it('should generate the correct message with external documentation link', () => {
const error = new TestError();
error.documentation = 'https://example.com/docs/test-error';
const error = new TestError('https://example.com/docs/test-error');
expect(error.message).toMatchInlineSnapshot(`
"This is a test error.
Expand All @@ -43,11 +42,10 @@ describe('StorybookError', () => {
});

it('should generate the correct message with multiple external documentation links', () => {
const error = new TestError();
error.documentation = [
const error = new TestError([
'https://example.com/docs/first-error',
'https://example.com/docs/second-error',
];
]);
expect(error.message).toMatchInlineSnapshot(`
"This is a test error.
Expand Down
26 changes: 11 additions & 15 deletions code/core/src/manager-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,26 @@ export enum Category {
}

export class ProviderDoesNotExtendBaseProviderError extends StorybookError {
readonly category = Category.MANAGER_UI;

readonly code = 1;

template() {
return `The Provider passed into Storybook's UI is not extended from the base Provider. Please check your Provider implementation.`;
constructor() {
super({
category: Category.MANAGER_UI,
code: 1,
message: `The Provider passed into Storybook's UI is not extended from the base Provider. Please check your Provider implementation.`,
});
}
}

export class UncaughtManagerError extends StorybookError {
readonly category = Category.MANAGER_UNCAUGHT;

readonly code = 1;

constructor(
public data: {
error: Error;
}
) {
super(data.error.message);
super({
category: Category.MANAGER_UNCAUGHT,
code: 1,
message: data.error.message,
});
this.stack = data.error.stack;
}

template() {
return this.message;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ describe('PreviewWeb', () => {
expect(vi.mocked(preview.view.showErrorDisplay).mock.calls[0][0]).toMatchInlineSnapshot(`
[SB_PREVIEW_API_0004 (MissingRenderToCanvasError): Expected your framework's preset to export a \`renderToCanvas\` field.
Perhaps it needs to be upgraded for Storybook 6.4?
Perhaps it needs to be upgraded for Storybook 7.0?
More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#mainjs-framework-field
]
Expand Down
Loading

0 comments on commit 3a88baa

Please sign in to comment.