diff --git a/code/core/src/component-testing/components/Panel.test.ts b/code/core/src/component-testing/components/Panel.test.ts index bcf177b349ab..6915e38d8ee2 100644 --- a/code/core/src/component-testing/components/Panel.test.ts +++ b/code/core/src/component-testing/components/Panel.test.ts @@ -259,6 +259,101 @@ describe('Panel', () => { ]); }); + it('does not hide waitFor after a collapsed step', () => { + const stepLog: LogItem[] = [ + { callId: 'story--id [0] step', status: CallStates.DONE, ancestors: [] }, + { + callId: 'story--id [0] step [0] click', + status: CallStates.DONE, + ancestors: ['story--id [0] step'], + }, + { callId: 'story--id [1] waitFor', status: CallStates.DONE, ancestors: [] }, + { + callId: 'story--id [1] waitFor [0] toHaveBeenCalledWith', + status: CallStates.DONE, + ancestors: ['story--id [1] waitFor'], + }, + ]; + const stepCalls = new Map( + [ + { + id: 'story--id [0] step', + storyId: 'story--id', + ancestors: [], + cursor: 0, + path: [], + method: 'step', + args: ['Fill and submit form'], + interceptable: true, + retain: false, + }, + { + id: 'story--id [0] step [0] click', + storyId: 'story--id', + ancestors: ['story--id [0] step'], + cursor: 0, + path: ['userEvent'], + method: 'click', + args: [], + interceptable: true, + retain: false, + }, + { + id: 'story--id [1] waitFor', + storyId: 'story--id', + ancestors: [], + cursor: 1, + path: [], + method: 'waitFor', + args: [], + interceptable: true, + retain: false, + }, + { + id: 'story--id [1] waitFor [0] toHaveBeenCalledWith', + storyId: 'story--id', + ancestors: ['story--id [1] waitFor'], + cursor: 0, + path: [], + method: 'toHaveBeenCalledWith', + args: [], + interceptable: true, + retain: false, + }, + ].map((v) => [v.id, v]) + ); + + const stepCollapsed = new Set(['story--id [0] step']); + + const result = getInteractions({ + log: stepLog, + calls: stepCalls, + collapsed: stepCollapsed, + setCollapsed, + }); + + // Step's child should be hidden + expect(result).toEqual([ + expect.objectContaining({ id: 'story--id [0] step', isHidden: false, isCollapsed: true }), + expect.objectContaining({ + id: 'story--id [0] step [0] click', + isHidden: true, + isCollapsed: false, + }), + // waitFor and its children should NOT be hidden by the collapsed step + expect.objectContaining({ + id: 'story--id [1] waitFor', + isHidden: false, + isCollapsed: false, + }), + expect.objectContaining({ + id: 'story--id [1] waitFor [0] toHaveBeenCalledWith', + isHidden: false, + isCollapsed: false, + }), + ]); + }); + it('uses status from log', () => { const withError = log.slice(0, 4).concat({ ...log[4], status: CallStates.ERROR }); diff --git a/code/core/src/instrumenter/instrumenter.test.ts b/code/core/src/instrumenter/instrumenter.test.ts index a3eea57e1da1..a8a2e0e2a97c 100644 --- a/code/core/src/instrumenter/instrumenter.test.ts +++ b/code/core/src/instrumenter/instrumenter.test.ts @@ -396,6 +396,60 @@ describe('Instrumenter', () => { ); }); + it('does not nest calls after an interceptable step into that step', async () => { + // Simulate the step function (intercept: true, async, takes a callback) + const { step } = instrument( + { + step: async (label: string, play: Function) => play(), + }, + { intercept: true } + ); + + // Simulate waitFor (intercept: true, takes a callback, calls it synchronously) + const { waitFor } = instrument( + { + waitFor: (callback: Function) => callback(), + }, + { intercept: true } + ); + + // Simulate an inner instrumented function (like expect().toHaveBeenCalledWith()) + const { fn1, fn2 } = instrument({ fn1: () => {}, fn2: () => {} }); + + setRenderPhase('playing'); + + // step("label", async () => { fn1() }) + await step('label', async () => { + fn1(); + }); + + // waitFor(() => { fn2() }) — should NOT be nested inside step + waitFor(() => { + fn2(); + }); + + // step itself should have ancestors: [] + expect(mocks.callSpy).toHaveBeenCalledWith( + expect.objectContaining({ method: 'step', ancestors: [] }) + ); + // fn1 inside step callback should have ancestors: [step_id] + const stepCall = mocks.callSpy.mock.calls.find((c: any) => c[0].method === 'step'); + const stepCallId = stepCall![0].id; + expect(mocks.callSpy).toHaveBeenCalledWith( + expect.objectContaining({ method: 'fn1', ancestors: [stepCallId] }) + ); + // waitFor should have ancestors: [] (NOT nested inside step) + expect(mocks.callSpy).toHaveBeenCalledWith( + expect.objectContaining({ method: 'waitFor', ancestors: [] }) + ); + // fn2 inside waitFor callback should have ancestors: [waitFor_id] only + const waitForCall = mocks.callSpy.mock.calls.find((c: any) => c[0].method === 'waitFor'); + const waitForCallId = waitForCall![0].id; + expect(mocks.callSpy).toHaveBeenCalledWith( + expect.objectContaining({ method: 'fn2', ancestors: [waitForCallId] }) + ); + }); + it('instruments the call result to support chaining', () => { const { fn1 } = instrument({ fn1: () => ({