Skip to content

Commit cfb926f

Browse files
committed
added tests
1 parent 13286c4 commit cfb926f

File tree

2 files changed

+633
-0
lines changed

2 files changed

+633
-0
lines changed
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import React from 'react';
3+
import { render, screen, fireEvent } from '@testing-library/react';
4+
import { FluentProvider, webLightTheme } from '@fluentui/react-components';
5+
6+
// Use vi.hoisted to define mock variables that are used in vi.mock factories
7+
const { mockPostMessage, mockDesignerIsDirty, mockChangeCount } = vi.hoisted(() => {
8+
return {
9+
mockPostMessage: vi.fn(),
10+
mockDesignerIsDirty: { current: false },
11+
mockChangeCount: { current: 0 },
12+
};
13+
});
14+
15+
// Mock webviewCommunication to avoid acquireVsCodeApi global
16+
vi.mock('../../../../webviewCommunication', async () => {
17+
const React = await import('react');
18+
return {
19+
VSCodeContext: React.createContext({ postMessage: mockPostMessage }),
20+
};
21+
});
22+
23+
vi.mock('@microsoft/logic-apps-designer-v2', () => ({
24+
serializeWorkflow: vi.fn(),
25+
store: { dispatch: vi.fn(), getState: vi.fn(() => ({ operations: { inputParameters: {} }, customCode: {} })) },
26+
serializeUnitTestDefinition: vi.fn(),
27+
getNodeOutputOperations: vi.fn(),
28+
useIsDesignerDirty: vi.fn(() => mockDesignerIsDirty.current),
29+
validateParameter: vi.fn(() => []),
30+
updateParameterValidation: vi.fn(),
31+
openPanel: vi.fn((arg: any) => arg),
32+
useAssertionsValidationErrors: vi.fn(() => ({})),
33+
useWorkflowParameterValidationErrors: vi.fn(() => ({})),
34+
useAllSettingsValidationErrors: vi.fn(() => ({})),
35+
useAllConnectionErrors: vi.fn(() => ({})),
36+
getCustomCodeFilesWithData: vi.fn(() => ({})),
37+
resetDesignerDirtyState: vi.fn(),
38+
resetDesignerView: vi.fn(),
39+
collapsePanel: vi.fn(),
40+
useChangeCount: vi.fn(() => mockChangeCount.current),
41+
}));
42+
43+
vi.mock('@microsoft/logic-apps-shared', () => ({
44+
isNullOrEmpty: vi.fn((val) => !val || Object.keys(val).length === 0),
45+
useThrottledEffect: vi.fn(),
46+
}));
47+
48+
vi.mock('@microsoft/vscode-extension-logic-apps', () => ({
49+
ExtensionCommand: {
50+
save: 'save',
51+
saveUnitTest: 'saveUnitTest',
52+
createUnitTest: 'createUnitTest',
53+
createUnitTestFromRun: 'createUnitTestFromRun',
54+
logTelemetry: 'logTelemetry',
55+
fileABug: 'fileABug',
56+
},
57+
}));
58+
59+
vi.mock('@tanstack/react-query', () => ({
60+
useMutation: vi.fn((fn: any) => ({
61+
mutate: vi.fn(() => fn?.()),
62+
isLoading: false,
63+
})),
64+
}));
65+
66+
vi.mock('../styles', () => ({
67+
useCommandBarStyles: vi.fn(() => ({
68+
viewModeContainer: 'viewModeContainer',
69+
viewButton: 'viewButton',
70+
selectedButton: 'selectedButton',
71+
})),
72+
}));
73+
74+
vi.mock('react-redux', () => ({
75+
useSelector: vi.fn(() => ({})),
76+
}));
77+
78+
vi.mock('../../../../intl', () => ({
79+
useIntlMessages: vi.fn(() => ({
80+
WORKFLOW_TAB: 'Workflow',
81+
CODE_TAB: 'Code',
82+
RUN_HISTORY_TAB: 'Run History',
83+
PUBLISH: 'Publish',
84+
PUBLISHING: 'Publishing...',
85+
SAVE: 'Save',
86+
DISCARD: 'Discard',
87+
DISCARD_SESSION_CHANGES: 'Discard session changes',
88+
DISCARD_DRAFT: 'Discard draft',
89+
SAVING_DRAFT: 'Saving...',
90+
ERROR_AUTOSAVING_DRAFT: 'Error autosaving draft',
91+
AUTOSAVED_SECONDS_AGO: 'Autosaved seconds ago',
92+
AUTOSAVED_MINUTES_AGO: 'Autosaved minutes ago',
93+
AUTOSAVED_ONE_HOUR_AGO: 'Autosaved 1 hour ago',
94+
SWITCH_TO_PUBLISHED: 'Switch to published',
95+
SWITCH_TO_DRAFT: 'Switch to draft',
96+
MORE_ACTIONS: 'More actions',
97+
PARAMETERS: 'Parameters',
98+
CONNECTIONS: 'Connections',
99+
ERRORS: 'Errors',
100+
SAVE_UNIT_TEST: 'Save unit test',
101+
CREATE_UNIT_TEST: 'Create unit test',
102+
CREATE_UNIT_TEST_FROM_RUN: 'Create unit test from run',
103+
UNIT_TEST_ASSERTIONS: 'Assertions',
104+
FILE_BUG: 'File a bug',
105+
})),
106+
useIntlFormatters: vi.fn(() => ({
107+
DRAFT_AUTOSAVED_AT: vi.fn(({ time }: any) => `Draft autosaved at ${time}`),
108+
AUTOSAVED_HOURS_AGO: vi.fn(({ count }: any) => `Autosaved ${count} hours ago`),
109+
})),
110+
designerMessages: {},
111+
}));
112+
113+
// Import after mocks
114+
import { DesignerCommandBar, type DesignerCommandBarProps } from '../indexV2';
115+
116+
const defaultProps: DesignerCommandBarProps = {
117+
isDarkMode: false,
118+
isUnitTest: false,
119+
isLocal: true,
120+
runId: '',
121+
saveWorkflow: vi.fn().mockResolvedValue({}),
122+
saveWorkflowFromCode: vi.fn().mockResolvedValue({}),
123+
discard: vi.fn(),
124+
isDesignerView: true,
125+
isCodeView: false,
126+
isMonitoringView: false,
127+
switchToDesignerView: vi.fn(),
128+
switchToCodeView: vi.fn(),
129+
switchToMonitoringView: vi.fn(),
130+
isDraftMode: true,
131+
saveDraftWorkflow: vi.fn(),
132+
discardDraft: vi.fn(),
133+
switchWorkflowMode: vi.fn(),
134+
lastDraftSaveTime: null,
135+
draftSaveError: null,
136+
isDraftSaving: false,
137+
hasDraft: false,
138+
};
139+
140+
const renderCommandBar = (props: Partial<DesignerCommandBarProps> = {}) => {
141+
return render(
142+
<FluentProvider theme={webLightTheme}>
143+
<DesignerCommandBar {...defaultProps} {...props} />
144+
</FluentProvider>
145+
);
146+
};
147+
148+
describe('DesignerCommandBar (V2)', () => {
149+
beforeEach(() => {
150+
vi.clearAllMocks();
151+
mockDesignerIsDirty.current = false;
152+
mockChangeCount.current = 0;
153+
});
154+
155+
describe('View mode tabs', () => {
156+
it('should render Workflow, Code, and Run History tabs', () => {
157+
renderCommandBar();
158+
expect(screen.getByText('Workflow')).toBeDefined();
159+
expect(screen.getByText('Code')).toBeDefined();
160+
expect(screen.getByText('Run History')).toBeDefined();
161+
});
162+
163+
it('should call switchToCodeView when Code tab is clicked', () => {
164+
const switchToCodeView = vi.fn();
165+
renderCommandBar({ switchToCodeView });
166+
fireEvent.click(screen.getByText('Code'));
167+
expect(switchToCodeView).toHaveBeenCalled();
168+
});
169+
170+
it('should call switchToMonitoringView when Run History tab is clicked', () => {
171+
const switchToMonitoringView = vi.fn();
172+
renderCommandBar({ switchToMonitoringView });
173+
fireEvent.click(screen.getByText('Run History'));
174+
expect(switchToMonitoringView).toHaveBeenCalled();
175+
});
176+
});
177+
178+
describe('Save/Publish button', () => {
179+
it('should display "Publish" when in draft mode', () => {
180+
mockDesignerIsDirty.current = true;
181+
renderCommandBar({ isDraftMode: true, hasDraft: true });
182+
expect(screen.getByText('Publish')).toBeDefined();
183+
});
184+
185+
it('should be disabled when in monitoring view', () => {
186+
renderCommandBar({ isMonitoringView: true });
187+
const publishBtn = screen.getByText('Publish').closest('button');
188+
expect(publishBtn?.disabled).toBe(true);
189+
});
190+
191+
it('should be disabled when not dirty and no draft', () => {
192+
mockDesignerIsDirty.current = false;
193+
renderCommandBar({ isDraftMode: true, hasDraft: false });
194+
const publishBtn = screen.getByText('Publish').closest('button');
195+
expect(publishBtn?.disabled).toBe(true);
196+
});
197+
198+
it('should be enabled when hasDraft even if not dirty', () => {
199+
mockDesignerIsDirty.current = false;
200+
renderCommandBar({ isDraftMode: true, hasDraft: true });
201+
const publishBtn = screen.getByText('Publish').closest('button');
202+
expect(publishBtn?.disabled).toBe(false);
203+
});
204+
205+
it('should be disabled when not in draft mode', () => {
206+
mockDesignerIsDirty.current = true;
207+
renderCommandBar({ isDraftMode: false, hasDraft: true });
208+
const publishBtn = screen.getByText('Publish').closest('button');
209+
expect(publishBtn?.disabled).toBe(true);
210+
});
211+
});
212+
213+
describe('Discard button', () => {
214+
it('should render simple discard button when no draft exists', () => {
215+
renderCommandBar({ hasDraft: false });
216+
// No dropdown menu items visible
217+
expect(screen.queryByText('Discard session changes')).toBeNull();
218+
expect(screen.queryByText('Discard draft')).toBeNull();
219+
});
220+
221+
it('should render discard dropdown menu when draft exists', () => {
222+
renderCommandBar({ hasDraft: true });
223+
// The discard button should be a menu trigger - click it to open
224+
const discardBtn = screen.getByLabelText('Discard');
225+
fireEvent.click(discardBtn);
226+
expect(screen.getByText('Discard session changes')).toBeDefined();
227+
expect(screen.getByText('Discard draft')).toBeDefined();
228+
});
229+
230+
it('should call discardDraft when "Discard draft" is clicked', () => {
231+
const discardDraft = vi.fn();
232+
renderCommandBar({ hasDraft: true, discardDraft });
233+
fireEvent.click(screen.getByLabelText('Discard'));
234+
fireEvent.click(screen.getByText('Discard draft'));
235+
expect(discardDraft).toHaveBeenCalled();
236+
});
237+
});
238+
239+
describe('Draft save notification', () => {
240+
it('should show "Saving..." when isDraftSaving', () => {
241+
renderCommandBar({ isDraftMode: true, isDraftSaving: true, isDesignerView: true });
242+
expect(screen.getByText('Saving...')).toBeDefined();
243+
});
244+
245+
it('should show error badge when draftSaveError exists', () => {
246+
renderCommandBar({ isDraftMode: true, draftSaveError: 'Network error', isDesignerView: true });
247+
expect(screen.getByText('Error autosaving draft')).toBeDefined();
248+
});
249+
250+
it('should not show notification when not in draft mode', () => {
251+
renderCommandBar({ isDraftMode: false, lastDraftSaveTime: Date.now(), isDesignerView: true });
252+
expect(screen.queryByText('Saving...')).toBeNull();
253+
});
254+
});
255+
256+
describe('Overflow menu', () => {
257+
it('should show "Switch to published" when hasDraft and isDraftMode', () => {
258+
renderCommandBar({ hasDraft: true, isDraftMode: true });
259+
fireEvent.click(screen.getByLabelText('More actions'));
260+
expect(screen.getByText('Switch to published')).toBeDefined();
261+
});
262+
263+
it('should show "Switch to draft" when hasDraft and not isDraftMode', () => {
264+
renderCommandBar({ hasDraft: true, isDraftMode: false });
265+
fireEvent.click(screen.getByLabelText('More actions'));
266+
expect(screen.getByText('Switch to draft')).toBeDefined();
267+
});
268+
269+
it('should not show switch options when no draft exists', () => {
270+
renderCommandBar({ hasDraft: false, isDraftMode: true });
271+
fireEvent.click(screen.getByLabelText('More actions'));
272+
expect(screen.queryByText('Switch to published')).toBeNull();
273+
expect(screen.queryByText('Switch to draft')).toBeNull();
274+
});
275+
276+
it('should call switchWorkflowMode(false) when "Switch to published" is clicked', () => {
277+
const switchWorkflowMode = vi.fn();
278+
renderCommandBar({ hasDraft: true, isDraftMode: true, switchWorkflowMode });
279+
fireEvent.click(screen.getByLabelText('More actions'));
280+
fireEvent.click(screen.getByText('Switch to published'));
281+
expect(switchWorkflowMode).toHaveBeenCalledWith(false);
282+
});
283+
284+
it('should call switchWorkflowMode(true) when "Switch to draft" is clicked', () => {
285+
const switchWorkflowMode = vi.fn();
286+
renderCommandBar({ hasDraft: true, isDraftMode: false, switchWorkflowMode });
287+
fireEvent.click(screen.getByLabelText('More actions'));
288+
fireEvent.click(screen.getByText('Switch to draft'));
289+
expect(switchWorkflowMode).toHaveBeenCalledWith(true);
290+
});
291+
292+
it('should show "File a bug" menu item', () => {
293+
renderCommandBar();
294+
fireEvent.click(screen.getByLabelText('More actions'));
295+
expect(screen.getByText('File a bug')).toBeDefined();
296+
});
297+
});
298+
});

0 commit comments

Comments
 (0)