diff --git a/code/addons/interactions/package.json b/code/addons/interactions/package.json index dd30fb2650d4..9deb3a01105f 100644 --- a/code/addons/interactions/package.json +++ b/code/addons/interactions/package.json @@ -62,6 +62,7 @@ "@devtools-ds/object-inspector": "^1.1.2", "@storybook/icons": "^1.2.5", "@types/node": "^22.0.0", + "ansi-to-html": "^0.7.2", "formik": "^2.2.9", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/code/addons/interactions/src/components/Interaction.tsx b/code/addons/interactions/src/components/Interaction.tsx index c9b557cddc68..610d0c1b032b 100644 --- a/code/addons/interactions/src/components/Interaction.tsx +++ b/code/addons/interactions/src/components/Interaction.tsx @@ -8,7 +8,7 @@ import { type Call, CallStates, type ControlStates } from '@storybook/instrument import { transparentize } from 'polished'; -import { isChaiError, isJestError } from '../utils'; +import { isChaiError, isJestError, useAnsiToHtmlFilter } from '../utils'; import type { Controls } from './InteractionsPanel'; import { MatcherResult } from './MatcherResult'; import { MethodCall } from './MethodCall'; @@ -116,6 +116,7 @@ const RowMessage = styled('div')(({ theme }) => ({ })); export const Exception = ({ exception }: { exception: Call['exception'] }) => { + const filter = useAnsiToHtmlFilter(); if (isJestError(exception)) { return ; } @@ -135,7 +136,7 @@ export const Exception = ({ exception }: { exception: Call['exception'] }) => { const more = paragraphs.length > 1; return ( -
{paragraphs[0]}
+

       {more && 

See the full stack trace in the browser console.

}
); diff --git a/code/addons/interactions/src/components/InteractionsPanel.tsx b/code/addons/interactions/src/components/InteractionsPanel.tsx index c86a1df8ffde..643732f250ca 100644 --- a/code/addons/interactions/src/components/InteractionsPanel.tsx +++ b/code/addons/interactions/src/components/InteractionsPanel.tsx @@ -6,7 +6,7 @@ import { type Call, CallStates, type ControlStates } from '@storybook/instrument import { transparentize } from 'polished'; -import { isTestAssertionError } from '../utils'; +import { isTestAssertionError, useAnsiToHtmlFilter } from '../utils'; import { Empty } from './EmptyState'; import { Interaction } from './Interaction'; import { Subnav } from './Subnav'; @@ -97,6 +97,7 @@ export const InteractionsPanel: React.FC = React.memo( onScrollToEnd, endRef, }) { + const filter = useAnsiToHtmlFilter(); return ( {(interactions.length > 0 || hasException) && ( @@ -131,9 +132,12 @@ export const InteractionsPanel: React.FC = React.memo( Caught exception in play function - - {printSerializedError(caughtException)} - + )} {unhandledErrors && ( diff --git a/code/addons/interactions/src/components/MatcherResult.tsx b/code/addons/interactions/src/components/MatcherResult.tsx index beae8b2146ff..46b5e540ad8d 100644 --- a/code/addons/interactions/src/components/MatcherResult.tsx +++ b/code/addons/interactions/src/components/MatcherResult.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { styled, typography } from 'storybook/internal/theming'; +import { useAnsiToHtmlFilter } from '../utils'; import { Node } from './MethodCall'; const getParams = (line: string, fromIndex = 0): string => { @@ -59,6 +60,7 @@ export const MatcherResult = ({ message: string; style?: React.CSSProperties; }) => { + const filter = useAnsiToHtmlFilter(); const lines = message.split('\n'); return (
{line}, 
]; + return [ + , +
, + ]; })}
); diff --git a/code/addons/interactions/src/utils.ts b/code/addons/interactions/src/utils.ts index 1b08eca12a24..d80d9f4cdbee 100644 --- a/code/addons/interactions/src/utils.ts +++ b/code/addons/interactions/src/utils.ts @@ -1,3 +1,7 @@ +import { type StorybookTheme, useTheme } from 'storybook/internal/theming'; + +import Filter from 'ansi-to-html'; + export function isTestAssertionError(error: unknown) { return isChaiError(error) || isJestError(error); } @@ -21,3 +25,15 @@ export function isJestError(error: unknown) { error.message.startsWith('expect(') ); } + +export function createAnsiToHtmlFilter(theme: StorybookTheme) { + return new Filter({ + fg: theme.color.defaultText, + bg: theme.background.content, + }); +} + +export function useAnsiToHtmlFilter() { + const theme = useTheme(); + return createAnsiToHtmlFilter(theme); +} diff --git a/code/renderers/react/src/__test__/portable-stories.test.tsx b/code/renderers/react/src/__test__/portable-stories.test.tsx index 94de89e093a5..85f33c9a714a 100644 --- a/code/renderers/react/src/__test__/portable-stories.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories.test.tsx @@ -8,7 +8,8 @@ import React from 'react'; import { addons } from 'storybook/internal/preview-api'; -import type { Meta } from '@storybook/react'; +import type { ProjectAnnotations } from '@storybook/csf'; +import type { Meta, ReactRenderer } from '@storybook/react'; import * as addonActionsPreview from '@storybook/addon-actions/preview'; @@ -124,7 +125,7 @@ describe('projectAnnotations', () => { const Story = composeStory( ButtonStories.WithActionArgType, ButtonStories.default, - addonActionsPreview + addonActionsPreview as ProjectAnnotations ); expect(Story.args.someActionArg).toHaveProperty('isAction', true); }); diff --git a/code/yarn.lock b/code/yarn.lock index 52f251440bb3..1c9424cca89c 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5492,6 +5492,7 @@ __metadata: "@storybook/instrumenter": "workspace:*" "@storybook/test": "workspace:*" "@types/node": "npm:^22.0.0" + ansi-to-html: "npm:^0.7.2" formik: "npm:^2.2.9" polished: "npm:^4.2.2" react: "npm:^18.2.0"