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

Release: Prerelease 8.0.0-alpha.8 #25449

Merged
merged 79 commits into from
Jan 6, 2024
Merged
Changes from 1 commit
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
6f48577
Remove action args TS magic in favor of explicit fn args
kasperpeulen Dec 15, 2023
a4edb55
Remove addSpies as implicit actions can not be used as spy anymore in…
kasperpeulen Dec 15, 2023
00bc7f8
Turn disallowImplicitActionsInRenderV8 feature flag on by default
kasperpeulen Dec 15, 2023
d1c850a
Remove argTypesRegex from sb init
kasperpeulen Dec 15, 2023
d1c2b24
Merge branch 'next' into kasper/remove-ts-magic
ndelangen Dec 19, 2023
7c71684
Make sure story play function fails if there are any unhandled error
kasperpeulen Dec 22, 2023
53dd0e8
Merge remote-tracking branch 'origin/kasper/remove-ts-magic' into kas…
kasperpeulen Dec 22, 2023
1654326
Merge remote-tracking branch 'origin/next' into kasper/remove-ts-magic
kasperpeulen Dec 22, 2023
146cd79
Adjust all tests
kasperpeulen Dec 22, 2023
093ef7d
Adjust tests
kasperpeulen Dec 27, 2023
7a6b26a
Assign explicit actions to CLI stories
kasperpeulen Dec 28, 2023
386a063
Make storybook/test a dev dependency of docs for the template stories
kasperpeulen Dec 28, 2023
6ca55dc
Fix svelte TS issues
kasperpeulen Dec 28, 2023
a3e57b5
Always use the test package
kasperpeulen Dec 28, 2023
719c230
Fix svelte stories
kasperpeulen Dec 28, 2023
c1dfbda
Rename event
kasperpeulen Dec 28, 2023
994b1e7
Remove fn functions from svelte
kasperpeulen Dec 28, 2023
ad70a36
Docs: Composition adjustments for the removal of the extract function
jonniebigodes Dec 28, 2023
04fd27c
FIx JS story vue3
kasperpeulen Dec 29, 2023
294302c
Add a story for unhandled errors
kasperpeulen Dec 29, 2023
49dd963
Don't paste as it doesn't work anymore in the new version of user event
kasperpeulen Dec 29, 2023
829df7a
Add e2e tests for unhandled errors
kasperpeulen Dec 29, 2023
1a0943b
Make sure vue shows play function errors in interactions panel
kasperpeulen Jan 2, 2024
d249074
Merge remote-tracking branch 'origin/next' into kasper/remove-ts-magic
kasperpeulen Jan 2, 2024
578adba
Fix vue renderer in build
kasperpeulen Jan 2, 2024
7ed846e
Ignore angular, as they have no implicit action errors
kasperpeulen Jan 2, 2024
bdf758d
Ignore angular, as they have no implicit action errors
kasperpeulen Jan 2, 2024
18ce80b
Update code/renderers/vue3/template/cli/ts-3-8/Button.stories.ts
kasperpeulen Jan 2, 2024
3f19146
Update scripts/release/__tests__/label-patches.test.ts
kasperpeulen Jan 2, 2024
748b7e4
Roll back svelte change
kasperpeulen Jan 2, 2024
e326e21
Merge remote-tracking branch 'origin/kasper/remove-ts-magic' into kas…
kasperpeulen Jan 2, 2024
90e96d0
Documentation over fn action arg
kasperpeulen Jan 3, 2024
d4efd04
during playing -> while playing
kasperpeulen Jan 3, 2024
e62ed8c
Merge branch 'next' into docs_remove_extract_command
jonniebigodes Jan 3, 2024
9ff64bc
Merge pull request #25238 from storybookjs/kasper/remove-ts-magic
kasperpeulen Jan 4, 2024
645439a
React: Remove deprecated setGlobalConfig portable stories api
yannbf Jan 3, 2024
d2105f6
Merge pull request #25442 from storybookjs/yann/remove-portable-stori…
yannbf Jan 4, 2024
8c305f9
Builder Vite: Remove StorybookViteConfig type in favor of StorybookCo…
yannbf Jan 3, 2024
4211d75
add svelte dependency to root
JReinhold Jan 4, 2024
7dd8b28
Merge branch 'next' into fix-linked-svelte-sandbox
JReinhold Jan 4, 2024
d4d9648
Merge pull request #25441 from storybookjs/yann/remove-storybook-conf…
yannbf Jan 4, 2024
5e7c185
remove type and reference for --static-dir flag
yannbf Jan 4, 2024
32ec844
Merge pull request #25455 from storybookjs/fix-linked-svelte-sandbox
JReinhold Jan 4, 2024
470d2c8
Merge pull request #25415 from storybookjs/yann/remove-static-dir-flag
yannbf Jan 4, 2024
38cc3ee
UI: Remove deprecated WithTooltip props
yannbf Jan 3, 2024
1185e6f
replace deprecated props in addon themes
yannbf Jan 3, 2024
e3070fc
add tests in tooltip stories
yannbf Jan 3, 2024
92b8215
resolve snapshot flakiness once and for all
yannbf Jan 3, 2024
caf1644
add chromatic delay
yannbf Jan 4, 2024
6ab3320
fix ts error in story
yannbf Jan 4, 2024
d3f2e43
remove play fn
yannbf Jan 4, 2024
cca26e9
Merge pull request #25440 from storybookjs/yann/remove-deprecated-wit…
yannbf Jan 4, 2024
a576dc7
Addon Links: Remove LinkTo from direct import
yannbf Jan 2, 2024
7802938
Merge pull request #25418 from storybookjs/yann/remove-linkto-import
yannbf Jan 4, 2024
ff47477
Types: Remove Story, ComponentStory, ComponentStoryObj, ComponentStor…
yannbf Jan 5, 2024
ab8f5c5
Merge branch 'next' into docs_remove_extract_command
jonniebigodes Jan 5, 2024
82640ba
Merge pull request #25371 from storybookjs/docs_remove_extract_command
jonniebigodes Jan 5, 2024
e4196ed
More details for styling and css docs
Jan 5, 2024
871d749
fix stories
yannbf Jan 5, 2024
4c7e545
add codemod to migration notes
yannbf Jan 5, 2024
fe96e7f
Update docs/configure/styling-and-css.md
Jan 5, 2024
656adbe
Merge pull request #25477 from storybookjs/yann/remove-deprecated-sto…
yannbf Jan 5, 2024
7acfd6d
Types: Remove Framework in favor of Renderer types
yannbf Jan 5, 2024
020035f
Add missing content
Jan 5, 2024
aa4547d
Merge pull request #25476 from storybookjs/yann/remove-deprecated-fra…
yannbf Jan 5, 2024
95231f0
Manager API: Remove deprecated navigateToSettingsPage method
yannbf Jan 4, 2024
da912f6
Merge pull request #25479 from storybookjs/docs/styling-and-css
Jan 5, 2024
cc0f31c
Merge pull request #25467 from storybookjs/yann/remove-deprecated-nav…
yannbf Jan 5, 2024
b50aea9
Core: Remove storyIndexers in favor of experimental_indexers
yannbf Jan 4, 2024
b8f8745
TypeScript: Remove deprecated addons module types
yannbf Jan 5, 2024
03215bb
Merge pull request #25468 from storybookjs/yann/remove-deprecated-sto…
yannbf Jan 5, 2024
64a8709
Addon docs: Remove deprecated parameters
yannbf Jan 5, 2024
4a3639c
Merge pull request #25469 from storybookjs/yann/remove-deprecated-doc…
yannbf Jan 5, 2024
e858258
Doc blocks: Remove deprecated props from Description block
yannbf Jan 4, 2024
c624a02
Merge pull request #25457 from storybookjs/yann/remove-deprecated-des…
yannbf Jan 5, 2024
e82fe80
Core: Remove collapseAll and expandAll methods
yannbf Jan 5, 2024
e8278ce
Merge pull request #25486 from storybookjs/yann/remove-expand-collaps…
yannbf Jan 5, 2024
718f6d4
Merge pull request #25485 from storybookjs/yann/remove-deprecated-add…
yannbf Jan 5, 2024
d70a3bf
Write changelog for 8.0.0-alpha.8 [skip ci]
storybook-bot Jan 5, 2024
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
Prev Previous commit
Next Next commit
Make sure story play function fails if there are any unhandled error
kasperpeulen committed Dec 22, 2023
commit 7c71684fbb4f5faed3bd47ecd1fba0be095ab405
20 changes: 13 additions & 7 deletions code/addons/interactions/src/Panel.tsx
Original file line number Diff line number Diff line change
@@ -4,10 +4,10 @@ import React, { Fragment, memo, useEffect, useMemo, useRef, useState } from 'rea
import { useAddonState, useChannel, useParameter } from '@storybook/manager-api';
import {
FORCE_REMOUNT,
IGNORED_EXCEPTION,
STORY_RENDER_PHASE_CHANGED,
STORY_THREW_EXCEPTION,
PLAY_FUNCTION_THREW_EXCEPTION,
UNHANDLED_ERRORS_DURING_PLAYING,
} from '@storybook/core-events';
import { EVENTS, type Call, CallStates, type LogItem } from '@storybook/instrumenter';

@@ -91,6 +91,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
hasException: false,
caughtException: undefined,
interactionsCount: 0,
unhandledErrors: undefined,
});

// local state
@@ -104,6 +105,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
interactions = [],
isPlaying = false,
caughtException = undefined,
unhandledErrors = undefined,
} = addonState;

// Log and calls are tracked in a ref so we don't needlessly rerender.
@@ -157,6 +159,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
hasException: false,
caughtException: undefined,
interactionsCount: 0,
unhandledErrors: undefined,
});
return;
}
@@ -180,11 +183,10 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
set((s) => ({ ...s, isErrored: true }));
},
[PLAY_FUNCTION_THREW_EXCEPTION]: (e) => {
if (e?.message !== IGNORED_EXCEPTION.message) {
set((s) => ({ ...s, caughtException: e }));
} else {
set((s) => ({ ...s, caughtException: undefined }));
}
set((s) => ({ ...s, caughtException: e }));
},
[UNHANDLED_ERRORS_DURING_PLAYING]: (e) => {
set((s) => ({ ...s, unhandledErrors: e }));
},
},
[collapsed]
@@ -224,7 +226,10 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
const [fileName] = storyFilePath.toString().split('/').slice(-1);
const scrollToTarget = () => scrollTarget?.scrollIntoView({ behavior: 'smooth', block: 'end' });

const hasException = !!caughtException || interactions.some((v) => v.status === CallStates.ERROR);
const hasException =
!!caughtException ||
!!unhandledErrors ||
interactions.some((v) => v.status === CallStates.ERROR);

if (isErrored) {
return <Fragment key="interactions" />;
@@ -240,6 +245,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
fileName={fileName}
hasException={hasException}
caughtException={caughtException}
unhandledErrors={unhandledErrors}
isPlaying={isPlaying}
pausedAt={pausedAt}
endRef={endRef}
6 changes: 3 additions & 3 deletions code/addons/interactions/src/components/Interaction.tsx
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import { MethodCall } from './MethodCall';
import { StatusIcon } from './StatusIcon';

import type { Controls } from './InteractionsPanel';
import { isJestError } from '../utils';

const MethodCallWrapper = styled.div(() => ({
fontFamily: typography.fonts.mono,
@@ -112,16 +113,15 @@ const RowMessage = styled('div')(({ theme }) => ({
},
}));

const Exception = ({ exception }: { exception: Call['exception'] }) => {
if (exception.message.startsWith('expect(')) {
export const Exception = ({ exception }: { exception: Call['exception'] }) => {
if (isJestError(exception)) {
return <MatcherResult {...exception} />;
}
const paragraphs = exception.message.split('\n\n');
const more = paragraphs.length > 1;
return (
<RowMessage>
<pre>{paragraphs[0]}</pre>

{exception.showDiff && exception.diff ? (
<>
<br />
55 changes: 43 additions & 12 deletions code/addons/interactions/src/components/InteractionsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable react/no-array-index-key */
import * as React from 'react';
import { Link, Placeholder } from '@storybook/components';
import { type Call, CallStates, type ControlStates } from '@storybook/instrumenter';
@@ -7,6 +8,7 @@ import { transparentize } from 'polished';
import { Subnav } from './Subnav';

import { Interaction } from './Interaction';
import { isTestAssertionError } from '../utils';

export interface Controls {
start: (args: any) => void;
@@ -30,23 +32,23 @@ interface InteractionsPanelProps {
fileName?: string;
hasException?: boolean;
caughtException?: Error;
unhandledErrors?: SerializedError[];
isPlaying?: boolean;
pausedAt?: Call['id'];
calls: Map<string, any>;
endRef?: React.Ref<HTMLDivElement>;
onScrollToEnd?: () => void;
}

const Container = styled.div<{ withException: boolean }>(({ theme, withException }) => ({
const Container = styled.div(({ theme }) => ({
minHeight: '100%',
background: theme.background.content,
...(withException && {
backgroundColor:
theme.base === 'dark' ? transparentize(0.93, theme.color.negative) : theme.background.warning,
}),
}));

const CaughtException = styled.div(({ theme }) => ({
borderBottom: `1px solid ${theme.appBorderColor}`,
backgroundColor:
theme.base === 'dark' ? transparentize(0.93, theme.color.negative) : theme.background.warning,
padding: 15,
fontSize: theme.typography.size.s2 - 1,
lineHeight: '19px',
@@ -69,9 +71,13 @@ const CaughtExceptionDescription = styled.p({
margin: 0,
padding: '0 0 20px',
});

const CaughtExceptionStack = styled.pre(({ theme }) => ({
margin: 0,
padding: 0,
'&:not(:last-child)': {
paddingBottom: 16,
},
fontSize: theme.typography.size.s1 - 1,
}));

@@ -84,13 +90,14 @@ export const InteractionsPanel: React.FC<InteractionsPanelProps> = React.memo(
fileName,
hasException,
caughtException,
unhandledErrors,
isPlaying,
pausedAt,
onScrollToEnd,
endRef,
}) {
return (
<Container withException={!!caughtException}>
<Container>
{(interactions.length > 0 || hasException) && (
<Subnav
controls={controls}
@@ -119,20 +126,34 @@ export const InteractionsPanel: React.FC<InteractionsPanelProps> = React.memo(
/>
))}
</div>
{caughtException && !caughtException.message?.startsWith('ignoredException') && (
{caughtException && !isTestAssertionError(caughtException) && (
<CaughtException>
<CaughtExceptionTitle>
Caught exception in <CaughtExceptionCode>play</CaughtExceptionCode> function
</CaughtExceptionTitle>
<CaughtExceptionDescription>
This story threw an error after it finished rendering which means your interactions
couldn&apos; t be run.Go to this story&apos; s play function in {fileName} to fix.
</CaughtExceptionDescription>
<CaughtExceptionStack data-chromatic="ignore">
{caughtException.stack || `${caughtException.name}: ${caughtException.message}`}
{printSerializedError(caughtException)}
</CaughtExceptionStack>
</CaughtException>
)}
{unhandledErrors && (
<CaughtException>
<CaughtExceptionTitle>Unhandled Errors</CaughtExceptionTitle>
<CaughtExceptionDescription>
Found {unhandledErrors.length} unhandled error{unhandledErrors.length > 1 ? 's' : ''}{' '}
while running the play function. This might cause false positive assertions. Resolve
unhandled errors or ignore unhandled errors with setting the
<CaughtExceptionCode>test.dangerouslyIgnoreUnhandledErrors</CaughtExceptionCode>{' '}
parameter to <CaughtExceptionCode>true</CaughtExceptionCode>.
</CaughtExceptionDescription>

{unhandledErrors.map((error, i) => (
<CaughtExceptionStack key={i} data-chromatic="ignore">
{printSerializedError(error)}
</CaughtExceptionStack>
))}
</CaughtException>
)}
<div ref={endRef} />
{!isPlaying && !caughtException && interactions.length === 0 && (
<Placeholder>
@@ -150,3 +171,13 @@ export const InteractionsPanel: React.FC<InteractionsPanelProps> = React.memo(
);
}
);

interface SerializedError {
name: string;
stack?: string;
message: string;
}

function printSerializedError(error: SerializedError) {
return error.stack || `${error.name}: ${error.message}`;
}
23 changes: 23 additions & 0 deletions code/addons/interactions/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export function isTestAssertionError(error: unknown) {
return isChaiError(error) || isJestError(error);
}

export function isChaiError(error: unknown) {
return (
error &&
typeof error === 'object' &&
'name' in error &&
typeof error.name === 'string' &&
error.name === 'AssertionError'
);
}

export function isJestError(error: unknown) {
return (
error &&
typeof error === 'object' &&
'message' in error &&
typeof error.message === 'string' &&
error.message.startsWith('expect(')
);
}
7 changes: 3 additions & 4 deletions code/lib/core-events/src/index.ts
Original file line number Diff line number Diff line change
@@ -38,6 +38,8 @@ enum events {
STORY_RENDER_PHASE_CHANGED = 'storyRenderPhaseChanged',
// Emitted when the play function throws
PLAY_FUNCTION_THREW_EXCEPTION = 'playFunctionThrewException',
// Emitted when there were unhandled errors during playing the story
UNHANDLED_ERRORS_DURING_PLAYING = 'unhandledErrorsDuringPlaying',
// Tell the story store to update (a subset of) a stories arg values
UPDATE_STORY_ARGS = 'updateStoryArgs',
// The values of a stories args just changed
@@ -88,6 +90,7 @@ export const {
GLOBALS_UPDATED,
NAVIGATE_URL,
PLAY_FUNCTION_THREW_EXCEPTION,
UNHANDLED_ERRORS_DURING_PLAYING,
PRELOAD_ENTRIES,
PREVIEW_BUILDER_PROGRESS,
PREVIEW_KEYDOWN,
@@ -124,10 +127,6 @@ export const {
TELEMETRY_ERROR,
} = events;

// Used to break out of the current render without showing a redbox
// eslint-disable-next-line local-rules/no-uncategorized-errors
export const IGNORED_EXCEPTION = new Error('ignoredException');

export interface WhatsNewCache {
lastDismissedPost?: string;
lastReadPost?: string;
16 changes: 2 additions & 14 deletions code/lib/instrumenter/src/instrumenter.ts
Original file line number Diff line number Diff line change
@@ -2,11 +2,10 @@
import type { Channel } from '@storybook/channels';
import { addons } from '@storybook/preview-api';
import type { StoryId } from '@storybook/types';
import { once, logger } from '@storybook/client-logger';
import { once } from '@storybook/client-logger';
import './typings.d.ts';
import {
FORCE_REMOUNT,
IGNORED_EXCEPTION,
SET_CURRENT_STORY,
STORY_RENDER_PHASE_CHANGED,
} from '@storybook/core-events';
@@ -403,10 +402,6 @@ export class Instrumenter {
}

invoke(fn: Function, object: Record<string, unknown>, call: Call, options: Options) {
// TODO this doesnt work because the abortSignal we have here is the newly created one
// const { abortSignal } = global.window.__STORYBOOK_PREVIEW__ || {};
// if (abortSignal && abortSignal.aborted) throw IGNORED_EXCEPTION;

const { callRefsByResult, renderPhase } = this.getState(call.storyId);

// Map complex values to a JSON-serializable representation.
@@ -475,7 +470,7 @@ export class Instrumenter {
diff = undefined,
actual = undefined,
expected = undefined,
} = processError(e);
} = e.name === 'AssertionError' ? processError(e) : e;

const exception = { name, message, stack, callId, showDiff, diff, actual, expected };
this.update({ ...info, status: CallStates.ERROR, exception });
@@ -495,13 +490,6 @@ export class Instrumenter {
}
throw e;
}

// We need to throw to break out of the play function, but we don't want to trigger a redbox
// so we throw an ignoredException, which is caught and silently ignored by Storybook.
if (e !== alreadyCompletedException) {
logger.warn(e);
throw IGNORED_EXCEPTION;
}
}
throw e;
};
28 changes: 25 additions & 3 deletions code/lib/preview-api/src/modules/preview-web/render/StoryRender.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-console */
import type {
Renderer,
RenderContext,
@@ -12,11 +13,11 @@ import type {
ViewMode,
} from '@storybook/types';
import type { Channel } from '@storybook/channels';
import { logger } from '@storybook/client-logger';
import {
STORY_RENDER_PHASE_CHANGED,
STORY_RENDERED,
PLAY_FUNCTION_THREW_EXCEPTION,
UNHANDLED_ERRORS_DURING_PLAYING,
} from '@storybook/core-events';
import type { StoryStore } from '../../store';
import type { Render, RenderType } from './Render';
@@ -218,22 +219,43 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer
this.notYetRendered = false;
if (abortSignal.aborted) return;

const ignoreUnhandledErrors =
this.story.parameters?.test?.dangerouslyIgnoreUnhandledErrors === true;

const unhandledErrors: Set<unknown> = new Set();
const onError = (event: ErrorEvent | PromiseRejectionEvent) =>
unhandledErrors.add('error' in event ? event.error : event.reason);

// The phase should be 'rendering' but it might be set to 'aborted' by another render cycle
if (this.renderOptions.autoplay && forceRemount && playFunction && this.phase !== 'errored') {
window.addEventListener('error', onError);
window.addEventListener('unhandledrejection', onError);
this.disableKeyListeners = true;
try {
await this.runPhase(abortSignal, 'playing', async () => {
await playFunction(renderContext.storyContext);
});
await this.runPhase(abortSignal, 'played');
if (!ignoreUnhandledErrors && unhandledErrors.size > 0) {
await this.runPhase(abortSignal, 'errored');
} else {
await this.runPhase(abortSignal, 'played');
}
} catch (error) {
logger.error(error);
await this.runPhase(abortSignal, 'errored', async () => {
this.channel.emit(PLAY_FUNCTION_THREW_EXCEPTION, serializeError(error));
});
if (this.story.parameters.throwPlayFunctionExceptions !== false) throw error;
console.error(error);
}
if (!ignoreUnhandledErrors && unhandledErrors.size > 0) {
this.channel.emit(
UNHANDLED_ERRORS_DURING_PLAYING,
Array.from(unhandledErrors).map(serializeError)
);
}
this.disableKeyListeners = false;
window.removeEventListener('unhandledrejection', onError);
window.removeEventListener('error', onError);
if (abortSignal.aborted) return;
}

2 changes: 1 addition & 1 deletion code/ui/manager/src/globals/exports.ts
Original file line number Diff line number Diff line change
@@ -139,7 +139,6 @@ export default {
'FORCE_REMOUNT',
'FORCE_RE_RENDER',
'GLOBALS_UPDATED',
'IGNORED_EXCEPTION',
'NAVIGATE_URL',
'PLAY_FUNCTION_THREW_EXCEPTION',
'PRELOAD_ENTRIES',
@@ -173,6 +172,7 @@ export default {
'STORY_UNCHANGED',
'TELEMETRY_ERROR',
'TOGGLE_WHATS_NEW_NOTIFICATIONS',
'UNHANDLED_ERRORS_DURING_PLAYING',
'UPDATE_GLOBALS',
'UPDATE_QUERY_PARAMS',
'UPDATE_STORY_ARGS',