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

Interactions: Show exceptions by non-instrumented code in panel #16592

Merged
merged 27 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4791962
Show exceptions by non-instrumented code in the Interactions panel
ghengeveld Nov 4, 2021
7641f83
Add story for Panel with caught exception
ghengeveld Jun 22, 2022
272e669
Fix spying on nested actions
ghengeveld Jun 23, 2022
e6df6cf
Only show yellow warning background for caught exceptions, not interc…
ghengeveld Jun 23, 2022
230bf59
Add aria-label for debugger controls
ghengeveld Jun 23, 2022
0749e59
Test debugger controls
ghengeveld Jun 23, 2022
099cc04
Deal with exceptions that aren't an Error
ghengeveld Jun 23, 2022
0f79748
Flatten components list and pull out InteractionsPanel from Panel
ghengeveld Jun 24, 2022
3c92b38
Pull TabStatus out of Panel
ghengeveld Jun 24, 2022
6473e97
Remove unused import
ghengeveld Jun 24, 2022
e890240
Update tests
ghengeveld Jun 24, 2022
8003c9b
Ignore deepscan error
ghengeveld Jun 24, 2022
5444a76
Fix serialization of object properties
ghengeveld Jun 24, 2022
6eff2be
Chromatic will fail when we deliberately throw an exception
ghengeveld Jun 24, 2022
f9178d7
Rename stories
ghengeveld Jun 24, 2022
a4ce716
Avoid stacked theme because it'll break interactions
ghengeveld Jun 24, 2022
e5b2c3d
Fix exception colors in dark theme
ghengeveld Jun 28, 2022
c530eae
Add unit tests for Panel.getInteractions
ghengeveld Jun 29, 2022
6e1e04c
Prevent Interactions panel from picking up exceptions that don't orig…
ghengeveld Jun 29, 2022
aafbfe6
Wait for idle before starting play function
ghengeveld Jun 29, 2022
d706766
Don't introduce an arbitrary await
ghengeveld Jun 29, 2022
eb0cd2a
Prevent flicker on the interactions counter
ghengeveld Jun 29, 2022
17d9034
Avoid running play function if story errored
ghengeveld Jun 29, 2022
a9b75cf
Monkey-patch callbacks instead of listening on the channel
ghengeveld Jun 30, 2022
96fb6c2
Better error message
ghengeveld Jul 1, 2022
c36d10c
Fix tests
ghengeveld Jul 1, 2022
9be5035
Ignore the stacktrace in Chromatic snapshots
ghengeveld Jul 1, 2022
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
265 changes: 265 additions & 0 deletions addons/interactions/src/Panel.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import { Call, CallStates, LogItem } from '@storybook/instrumenter';
import { getInteractions } from './Panel';

describe('Panel', () => {
describe('getInteractions', () => {
const log: LogItem[] = [
{
callId: 'story--id [4] findByText',
status: CallStates.DONE,
},
{
callId: 'story--id [5] click',
status: CallStates.DONE,
},
{
callId: 'story--id [6] waitFor',
status: CallStates.DONE,
},
{
callId: 'story--id [6] waitFor [2] toHaveBeenCalledWith',
parentId: 'story--id [6] waitFor',
status: CallStates.DONE,
},
];
const calls = new Map<Call['id'], Call>(
[
{
id: 'story--id [0] action',
storyId: 'story--id',
cursor: 0,
path: [],
method: 'action',
args: [{ __function__: { name: 'onSubmit' } }],
interceptable: false,
retain: true,
},
{
id: 'story--id [1] action',
storyId: 'story--id',
cursor: 1,
path: [],
method: 'action',
args: [{ __function__: { name: 'onTransactionStart' } }],
interceptable: false,
retain: true,
},
{
id: 'story--id [2] action',
storyId: 'story--id',
cursor: 2,
path: [],
method: 'action',
args: [{ __function__: { name: 'onTransactionEnd' } }],
interceptable: false,
retain: true,
},
{
id: 'story--id [3] within',
storyId: 'story--id',
cursor: 3,
path: [],
method: 'within',
args: [{ __element__: { localName: 'div', id: 'root', innerText: 'Click' } }],
interceptable: false,
retain: false,
},
{
id: 'story--id [4] findByText',
storyId: 'story--id',
cursor: 4,
path: [{ __callId__: 'story--id [3] within' }],
method: 'findByText',
args: ['Click'],
interceptable: true,
retain: false,
},
{
id: 'story--id [5] click',
storyId: 'story--id',
cursor: 5,
path: ['userEvent'],
method: 'click',
args: [{ __element__: { localName: 'button', innerText: 'Click' } }],
interceptable: true,
retain: false,
},
{
id: 'story--id [6] waitFor [0] expect',
parentId: 'story--id [6] waitFor',
storyId: 'story--id',
cursor: 0,
path: [],
method: 'expect',
args: [{ __callId__: 'story--id [0] action', retain: true }],
interceptable: true,
retain: false,
},
{
id: 'story--id [6] waitFor [1] stringMatching',
parentId: 'story--id [6] waitFor',
storyId: 'story--id',
cursor: 1,
path: ['expect'],
method: 'stringMatching',
args: [{ __regexp__: { flags: 'gi', source: '([A-Z])\\w+' } }],
interceptable: false,
retain: false,
},
{
id: 'story--id [6] waitFor [2] toHaveBeenCalledWith',
parentId: 'story--id [6] waitFor',
storyId: 'story--id',
cursor: 2,
path: [{ __callId__: 'story--id [6] waitFor [0] expect' }],
method: 'toHaveBeenCalledWith',
args: [{ __callId__: 'story--id [6] waitFor [1] stringMatching', retain: false }],
interceptable: true,
retain: false,
},
{
id: 'story--id [6] waitFor',
storyId: 'story--id',
cursor: 6,
path: [],
method: 'waitFor',
args: [{ __function__: { name: '' } }],
interceptable: true,
retain: false,
},
].map((v) => [v.id, v])
);
const collapsed = new Set<Call['id']>();
const setCollapsed = () => {};

it('returns list of interactions', () => {
expect(getInteractions({ log, calls, collapsed, setCollapsed })).toEqual([
{
...calls.get('story--id [4] findByText'),
status: CallStates.DONE,
childCallIds: undefined,
isCollapsed: false,
toggleCollapsed: expect.any(Function),
},
{
...calls.get('story--id [5] click'),
status: CallStates.DONE,
childCallIds: undefined,
isCollapsed: false,
toggleCollapsed: expect.any(Function),
},
{
...calls.get('story--id [6] waitFor'),
status: CallStates.DONE,
childCallIds: ['story--id [6] waitFor [2] toHaveBeenCalledWith'],
isCollapsed: false,
toggleCollapsed: expect.any(Function),
},
{
...calls.get('story--id [6] waitFor [2] toHaveBeenCalledWith'),
status: CallStates.DONE,
childCallIds: undefined,
isCollapsed: false,
toggleCollapsed: expect.any(Function),
},
]);
});

it('omits calls for which the parent is collapsed', () => {
const withCollapsed = new Set<Call['id']>(['story--id [6] waitFor']);

expect(getInteractions({ log, calls, collapsed: withCollapsed, setCollapsed })).toEqual([
expect.objectContaining({
...calls.get('story--id [4] findByText'),
childCallIds: undefined,
isCollapsed: false,
}),
expect.objectContaining({
...calls.get('story--id [5] click'),
childCallIds: undefined,
isCollapsed: false,
}),
expect.objectContaining({
...calls.get('story--id [6] waitFor'),
childCallIds: ['story--id [6] waitFor [2] toHaveBeenCalledWith'],
isCollapsed: true,
}),
]);
});

it('uses status from log', () => {
const withError = log.slice(0, 3).concat({ ...log[3], status: CallStates.ERROR });

expect(getInteractions({ log: withError, calls, collapsed, setCollapsed })).toEqual([
expect.objectContaining({
id: 'story--id [4] findByText',
status: CallStates.DONE,
}),
expect.objectContaining({
id: 'story--id [5] click',
status: CallStates.DONE,
}),
expect.objectContaining({
id: 'story--id [6] waitFor',
status: CallStates.DONE,
}),
expect.objectContaining({
id: 'story--id [6] waitFor [2] toHaveBeenCalledWith',
status: CallStates.ERROR,
}),
]);
});

it('keeps status active for errored child calls while parent is active', () => {
const withActiveError = log.slice(0, 2).concat([
{ ...log[2], status: CallStates.ACTIVE },
{ ...log[3], status: CallStates.ERROR },
]);

expect(getInteractions({ log: withActiveError, calls, collapsed, setCollapsed })).toEqual([
expect.objectContaining({
id: 'story--id [4] findByText',
status: CallStates.DONE,
}),
expect.objectContaining({
id: 'story--id [5] click',
status: CallStates.DONE,
}),
expect.objectContaining({
id: 'story--id [6] waitFor',
status: CallStates.ACTIVE,
}),
expect.objectContaining({
id: 'story--id [6] waitFor [2] toHaveBeenCalledWith',
status: CallStates.ACTIVE, // not ERROR
}),
]);
});

it('does not override child status other than error for active parent', () => {
const withActiveWaiting = log.slice(0, 2).concat([
{ ...log[2], status: CallStates.ACTIVE },
{ ...log[3], status: CallStates.WAITING },
]);

expect(getInteractions({ log: withActiveWaiting, calls, collapsed, setCollapsed })).toEqual([
expect.objectContaining({
id: 'story--id [4] findByText',
status: CallStates.DONE,
}),
expect.objectContaining({
id: 'story--id [5] click',
status: CallStates.DONE,
}),
expect.objectContaining({
id: 'story--id [6] waitFor',
status: CallStates.ACTIVE,
}),
expect.objectContaining({
id: 'story--id [6] waitFor [2] toHaveBeenCalledWith',
status: CallStates.WAITING,
}),
]);
});
});
});
Loading