Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core: Make sure StorybookError message shows up in browser console and interactions panel #28464

Merged
merged 13 commits into from
Jul 7, 2024
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,
template: '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
54 changes: 23 additions & 31 deletions code/core/src/preview-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,42 +352,34 @@ export class NextJsSharpError extends StorybookError {
}

export class NextjsRouterMocksNotAvailable extends StorybookError {
readonly category = Category.FRAMEWORK_NEXTJS;

readonly code = 2;

constructor(public data: { importType: string }) {
super();
}

template() {
return dedent`
Tried to access router mocks from "${this.data.importType}" but they were not created yet. You might be running code in an unsupported environment.
`;
super({
category: Category.FRAMEWORK_NEXTJS,
code: 2,
template: dedent`
Tried to access router mocks from "${data.importType}" but they were not created yet. You might be running code in an unsupported environment.
`,
});
}
}

export class UnknownArgTypesError extends StorybookError {
readonly category = Category.DOCS_TOOLS;

readonly code = 1;

readonly documentation = 'https://github.com/storybookjs/storybook/issues/26606';

constructor(public data: { type: object; language: string }) {
super();
}

template() {
return dedent`There was a failure when generating detailed ArgTypes in ${
this.data.language
} for:

${JSON.stringify(this.data.type, null, 2)}

Storybook will fall back to use a generic type description instead.

This type is either not supported or it is a bug in the docgen generation in Storybook.
If you think this is a bug, please detail it as much as possible in the Github issue.`;
super({
category: Category.DOCS_TOOLS,
code: 1,
documentation: 'https://github.com/storybookjs/storybook/issues/26606',
template: dedent`
There was a failure when generating detailed ArgTypes in ${data.language} for:
${JSON.stringify(data.type, null, 2)}

Storybook will fall back to use a generic type description instead.

This type is either not supported or it is a bug in the docgen generation in Storybook.
If you think this is a bug, please detail it as much as possible in the Github issue.

More info: https://github.com/storybookjs/storybook/issues/26606
`,
});
}
}
56 changes: 37 additions & 19 deletions code/core/src/storybook-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@ export abstract class StorybookError extends Error {
/**
* Category of the error. Used to classify the type of error, e.g., 'PREVIEW_API'.
*/
abstract readonly category: string;
public readonly category: string;

/**
* Code representing the error. Used to uniquely identify the error, e.g., 1.
*/
abstract readonly code: number;

/**
* A properly written error message template for this error.
* @see https://github.com/storybookjs/storybook/blob/next/code/lib/core-events/src/errors/README.md#how-to-write-a-proper-error-message
*/
abstract template(): string;
public readonly code: number;

/**
* Data associated with the error. Used to provide additional information in the error message or to be passed to telemetry.
Expand All @@ -26,16 +20,15 @@ export abstract class StorybookError extends Error {
* - If a string, uses the provided URL for documentation (external or FAQ links).
* - If `false` (default), no documentation link is added.
*/
public documentation: boolean | string | string[] = false;
public documentation: boolean | string | string[];
kasperpeulen marked this conversation as resolved.
Show resolved Hide resolved

/**
* Flag used to easily determine if the error originates from Storybook.
*/
readonly fromStorybook: true = true as const;

get fullErrorCode() {
const paddedCode = String(this.code).padStart(4, '0');
return `SB_${this.category}_${paddedCode}` as `SB_${this['category']}_${string}`;
return fullErrorCode({ code: this.code, category: this.category });
}

/**
Expand All @@ -47,20 +40,45 @@ export abstract class StorybookError extends Error {
return `${this.fullErrorCode} (${errorName})`;
}

constructor(props: {
category: string;
code: number;
template: string;
documentation?: boolean | string | string[];
}) {
super(StorybookError.message(props));
this.category = props.category;
this.documentation = props.documentation ?? false;
this.code = props.code;
}

/**
* Generates the error message along with additional documentation link (if applicable).
*/
get message() {
static message({
documentation,
code,
category,
template,
}: ConstructorParameters<typeof StorybookError>[0]) {
let page: string | undefined;

if (this.documentation === true) {
page = `https://storybook.js.org/error/${this.fullErrorCode}`;
} else if (typeof this.documentation === 'string') {
page = this.documentation;
} else if (Array.isArray(this.documentation)) {
page = `\n${this.documentation.map((doc) => `\t- ${doc}`).join('\n')}`;
if (documentation === true) {
page = `https://storybook.js.org/error/${fullErrorCode({ code, category })}`;
} else if (typeof documentation === 'string') {
page = documentation;
} else if (Array.isArray(documentation)) {
page = `\n${documentation.map((doc) => `\t- ${doc}`).join('\n')}`;
}

return `${this.template()}${page != null ? `\n\nMore info: ${page}\n` : ''}`;
return `${template}${page != null ? `\n\nMore info: ${page}\n` : ''}`;
}
}

function fullErrorCode({
code,
category,
}: Pick<StorybookError, 'code' | 'category'>): `SB_${typeof category}_${string}` {
const paddedCode = String(code).padStart(4, '0');
return `SB_${category}_${paddedCode}`;
}
Loading