diff --git a/src/DetailsView/components/auto-detected-failures-dialog.tsx b/src/DetailsView/components/auto-detected-failures-dialog.tsx index fd8e44a6251..fc0c05e6fe0 100644 --- a/src/DetailsView/components/auto-detected-failures-dialog.tsx +++ b/src/DetailsView/components/auto-detected-failures-dialog.tsx @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Checkbox, Dialog, DialogFooter, DialogType, PrimaryButton } from '@fluentui/react'; +import { Checkbox, Dialog, DialogFooter, DialogType, PrimaryButton, Stack } from '@fluentui/react'; import { UserConfigMessageCreator } from 'common/message-creators/user-config-message-creator'; import { UserConfigurationStoreData } from 'common/types/store-data/user-configuration-store'; import { VisualizationScanResultData } from 'common/types/store-data/visualization-scan-result-data'; @@ -94,15 +94,27 @@ export class AutoDetectedFailuresDialog extends React.Component< - - + + + + + + + + ); diff --git a/src/DetailsView/components/details-view-command-bar.tsx b/src/DetailsView/components/details-view-command-bar.tsx index 0a243213fc5..1f401894305 100644 --- a/src/DetailsView/components/details-view-command-bar.tsx +++ b/src/DetailsView/components/details-view-command-bar.tsx @@ -6,6 +6,7 @@ import { NewTabLinkWithTooltip } from 'common/components/new-tab-link-with-toolt import { VisualizationConfigurationFactory } from 'common/configs/visualization-configuration-factory'; import { CardsViewModel } from 'common/types/store-data/card-view-model'; import { ScanMetadata } from 'common/types/store-data/unified-data-interface'; +import { UserConfigurationStoreData } from 'common/types/store-data/user-configuration-store'; import { TabStopRequirementState } from 'common/types/store-data/visualization-scan-result-data'; import { VisualizationStoreData } from 'common/types/store-data/visualization-store-data'; import { VersionedAssessmentData } from 'common/types/versioned-assessment-data'; @@ -91,6 +92,7 @@ export interface DetailsViewCommandBarProps { visualizationConfigurationFactory: VisualizationConfigurationFactory; selectedTest: VisualizationType; tabStopRequirementData: TabStopRequirementState; + userConfigurationStoreData: UserConfigurationStoreData; } export class DetailsViewCommandBar extends React.Component< DetailsViewCommandBarProps, diff --git a/src/DetailsView/components/details-view-overlay/settings-panel/settings/extension-settings-provider.ts b/src/DetailsView/components/details-view-overlay/settings-panel/settings/extension-settings-provider.ts index 3c2e002abd6..a4ddb358389 100644 --- a/src/DetailsView/components/details-view-overlay/settings-panel/settings/extension-settings-provider.ts +++ b/src/DetailsView/components/details-view-overlay/settings-panel/settings/extension-settings-provider.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { title } from 'content/strings/application'; import { AutoDetectedFailuresDialogSettings } from 'DetailsView/components/details-view-overlay/settings-panel/settings/auto-detected-failures-dialog/auto-detected-failures-dialog-settings'; +import { SaveAssessmentDialogSettings } from 'DetailsView/components/details-view-overlay/settings-panel/settings/save-assessment-dialog/save-assessment-dialog-settings'; import { createTelemetrySettings } from 'DetailsView/components/details-view-overlay/settings-panel/settings/telemetry/telemetry-settings'; import { HighContrastSettings } from './high-contrast/high-contrast-settings'; import { IssueFilingSettings } from './issue-filing/issue-filing-settings'; @@ -11,5 +12,6 @@ export const ExtensionSettingsProvider = createSettingsProvider([ createTelemetrySettings(title), HighContrastSettings, AutoDetectedFailuresDialogSettings, + SaveAssessmentDialogSettings, IssueFilingSettings, ]); diff --git a/src/DetailsView/components/details-view-overlay/settings-panel/settings/save-assessment-dialog/save-assessment-dialog-settings.tsx b/src/DetailsView/components/details-view-overlay/settings-panel/settings/save-assessment-dialog/save-assessment-dialog-settings.tsx new file mode 100644 index 00000000000..7c594e76e1b --- /dev/null +++ b/src/DetailsView/components/details-view-overlay/settings-panel/settings/save-assessment-dialog/save-assessment-dialog-settings.tsx @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { NamedFC } from 'common/react/named-fc'; +import * as React from 'react'; +import { GenericToggle } from '../../../../generic-toggle'; +import { SettingsProps } from '../settings-props'; + +export const SaveAssessmentDialogSettings = NamedFC( + 'SaveAssessmentDialog', + props => { + return ( + + props.deps.userConfigMessageCreator.setSaveAssessmentDialogState(state) + } + /> + ); + }, +); diff --git a/src/DetailsView/components/save-assessment-button-factory.tsx b/src/DetailsView/components/save-assessment-button-factory.tsx index 47a18bdef7d..541ceb7b57a 100644 --- a/src/DetailsView/components/save-assessment-button-factory.tsx +++ b/src/DetailsView/components/save-assessment-button-factory.tsx @@ -4,6 +4,7 @@ import { AssessmentDataFormatter } from 'common/assessment-data-formatter'; import { FileNameBuilder } from 'common/filename-builder'; import { AssessmentStoreData } from 'common/types/store-data/assessment-result-data'; import { TabStoreData } from 'common/types/store-data/tab-store-data'; +import { UserConfigurationStoreData } from 'common/types/store-data/user-configuration-store'; import { SaveAssessmentButton, SaveAssessmentButtonDeps, @@ -22,6 +23,7 @@ export type SaveAssessmentButtonFactoryProps = { deps: SaveAssessmentButtonFactoryDeps; assessmentStoreData: AssessmentStoreData; tabStoreData: TabStoreData; + userConfigurationStoreData: UserConfigurationStoreData; }; export function getSaveButtonForAssessment(props: SaveAssessmentButtonFactoryProps): JSX.Element { diff --git a/src/DetailsView/components/save-assessment-button.tsx b/src/DetailsView/components/save-assessment-button.tsx index 38261aa8fca..390ae984163 100644 --- a/src/DetailsView/components/save-assessment-button.tsx +++ b/src/DetailsView/components/save-assessment-button.tsx @@ -1,29 +1,93 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { Checkbox, Dialog, DialogFooter, DialogType, PrimaryButton, Stack } from '@fluentui/react'; +import { useBoolean } from '@fluentui/react-hooks'; import { InsightsCommandButton } from 'common/components/controls/insights-command-button'; +import { UserConfigMessageCreator } from 'common/message-creators/user-config-message-creator'; +import { NamedFC } from 'common/react/named-fc'; +import { UserConfigurationStoreData } from 'common/types/store-data/user-configuration-store'; import { DetailsViewActionMessageCreator } from 'DetailsView/actions/details-view-action-message-creator'; +import styles from 'DetailsView/components/common-dialog-styles.scss'; import * as React from 'react'; export interface SaveAssessmentButtonDeps { detailsViewActionMessageCreator: DetailsViewActionMessageCreator; + userConfigMessageCreator: UserConfigMessageCreator; } export interface SaveAssessmentButtonProps { download: string; href: string; deps: SaveAssessmentButtonDeps; + userConfigurationStoreData: UserConfigurationStoreData; } -export class SaveAssessmentButton extends React.Component { - public render(): JSX.Element { +export const SaveAssessmentButton = NamedFC( + 'SaveAssessmentButton', + props => { + const [dialogHidden, { setTrue: hideDialog, setFalse: showDialog }] = useBoolean(true); + + function handleSaveAssessmentClick(event: React.MouseEvent) { + props.deps.detailsViewActionMessageCreator.saveAssessment(event); + if (props.userConfigurationStoreData.showSaveAssessmentDialog) { + showDialog(); + } + } + + function handleDontShowAgainClick(event: React.MouseEvent, checked?: boolean) { + if (checked === undefined) return; + props.deps.userConfigMessageCreator.setSaveAssessmentDialogState(!checked); + } + return ( - - Save assessment - + <> + + Save assessment + + + ); - } -} + }, +); diff --git a/src/background/actions/action-payloads.ts b/src/background/actions/action-payloads.ts index 2ca8c8c9869..eba989306f3 100644 --- a/src/background/actions/action-payloads.ts +++ b/src/background/actions/action-payloads.ts @@ -256,3 +256,7 @@ export interface SaveWindowBoundsPayload extends BaseActionPayload { export interface AutoDetectedFailuresDialogStatePayload extends BaseActionPayload { enabled: boolean; } + +export interface SaveAssessmentDialogStatePayload extends BaseActionPayload { + enabled: boolean; +} diff --git a/src/background/actions/user-configuration-actions.ts b/src/background/actions/user-configuration-actions.ts index 1eef4677f62..e5485b411d7 100644 --- a/src/background/actions/user-configuration-actions.ts +++ b/src/background/actions/user-configuration-actions.ts @@ -3,6 +3,7 @@ import { AsyncAction } from 'common/flux/async-action'; import { AutoDetectedFailuresDialogStatePayload, + SaveAssessmentDialogStatePayload, SaveIssueFilingSettingsPayload, SaveWindowBoundsPayload, SetHighContrastModePayload, @@ -23,4 +24,6 @@ export class UserConfigurationActions { public readonly saveWindowBounds = new AsyncAction(); public readonly setAutoDetectedFailuresDialogState = new AsyncAction(); + public readonly setSaveAssessmentDialogState = + new AsyncAction(); } diff --git a/src/background/global-action-creators/registrar/register-user-configuration-message-callbacks.ts b/src/background/global-action-creators/registrar/register-user-configuration-message-callbacks.ts index 5e0990b16e2..c473741520e 100644 --- a/src/background/global-action-creators/registrar/register-user-configuration-message-callbacks.ts +++ b/src/background/global-action-creators/registrar/register-user-configuration-message-callbacks.ts @@ -50,4 +50,8 @@ export const registerUserConfigurationMessageCallback = ( Messages.UserConfig.SetAutoDetectedFailuresDialogState, userConfigurationActionCreator.setAutoDetectedFailuresDialogState, ); + interpreter.registerTypeToPayloadCallback( + Messages.UserConfig.SetSaveAssessmentDialogState, + userConfigurationActionCreator.setSaveAssessmentDialogState, + ); }; diff --git a/src/background/global-action-creators/user-configuration-action-creator.ts b/src/background/global-action-creators/user-configuration-action-creator.ts index 39709506951..629a94025be 100644 --- a/src/background/global-action-creators/user-configuration-action-creator.ts +++ b/src/background/global-action-creators/user-configuration-action-creator.ts @@ -5,6 +5,7 @@ import { TelemetryEventHandler } from 'background/telemetry/telemetry-event-hand import * as TelemetryEvents from '../../common/extension-telemetry-events'; import { AutoDetectedFailuresDialogStatePayload, + SaveAssessmentDialogStatePayload, SaveIssueFilingSettingsPayload, SaveWindowBoundsPayload, SetHighContrastModePayload, @@ -61,4 +62,14 @@ export class UserConfigurationActionCreator { payload, ); }; + + public setSaveAssessmentDialogState = async ( + payload: SaveAssessmentDialogStatePayload, + ): Promise => { + await this.userConfigActions.setSaveAssessmentDialogState.invoke(payload); + this.telemetryEventHandler.publishTelemetry( + TelemetryEvents.SET_SAVE_ASSESSMENT_DIALOG_STATE, + payload, + ); + }; } diff --git a/src/background/stores/global/user-configuration-store.ts b/src/background/stores/global/user-configuration-store.ts index ca97fc8ebca..9a5aea74465 100644 --- a/src/background/stores/global/user-configuration-store.ts +++ b/src/background/stores/global/user-configuration-store.ts @@ -8,6 +8,7 @@ import { StoreNames } from '../../../common/stores/store-names'; import { UserConfigurationStoreData } from '../../../common/types/store-data/user-configuration-store'; import { AutoDetectedFailuresDialogStatePayload, + SaveAssessmentDialogStatePayload, SaveIssueFilingSettingsPayload, SaveWindowBoundsPayload, SetHighContrastModePayload, @@ -30,6 +31,7 @@ export class UserConfigurationStore extends PersistentStore => { @@ -159,4 +164,11 @@ export class UserConfigurationStore extends PersistentStore => { + this.state.showSaveAssessmentDialog = payload.enabled; + await this.emitChanged(); + }; } diff --git a/src/common/extension-telemetry-events.ts b/src/common/extension-telemetry-events.ts index ac512dbb88c..d9e116dcfd9 100644 --- a/src/common/extension-telemetry-events.ts +++ b/src/common/extension-telemetry-events.ts @@ -80,6 +80,7 @@ export const LEFT_NAV_PANEL_EXPANDED: string = 'leftNavPanelExpanded'; export const NEEDS_REVIEW_TOGGLE: string = 'NeedsReviewToggled'; export const NAVIGATE_TO_NEW_CARDS_VIEW: string = 'NavigateToNewCardsView'; export const SET_AUTO_DETECTED_FAILURES_DIALOG_STATE: string = 'setAutoDetectedFailuresDialogState'; +export const SET_SAVE_ASSESSMENT_DIALOG_STATE: string = 'setSaveAssessmentDialogState'; export const UNHANDLED_ERROR: string = 'unhandledError'; export const ACCESSIBLENAMES_TOGGLE: string = 'accessibleNamesToggled'; @@ -285,6 +286,10 @@ export type AutoDetectedFailuresDialogStateTelemetryData = { enabled: boolean; }; +export type ShowAssessmentDialogStateTelemetryData = { + enabled: boolean; +}; + export enum ErrorType { WindowError = 'WindowError', UnhandledRejection = 'UnhandledRejection', @@ -327,4 +332,5 @@ export type TelemetryData = | SetAllUrlsPermissionTelemetryData | TabStopsAutomatedResultsTelemetryData | AutoDetectedFailuresDialogStateTelemetryData + | ShowAssessmentDialogStateTelemetryData | UnhandledErrorTelemetryData; diff --git a/src/common/message-creators/user-config-message-creator.ts b/src/common/message-creators/user-config-message-creator.ts index caa0893bc74..41d6fe05554 100644 --- a/src/common/message-creators/user-config-message-creator.ts +++ b/src/common/message-creators/user-config-message-creator.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { AutoDetectedFailuresDialogStatePayload, + SaveAssessmentDialogStatePayload, SaveIssueFilingSettingsPayload, SaveWindowBoundsPayload, SetAdbLocationPayload, @@ -103,4 +104,16 @@ export class UserConfigMessageCreator { payload, }); } + + public setSaveAssessmentDialogState(showDialog: boolean): void { + const telemetry = this.telemetryFactory.forSetShowAssessmentDialogState(showDialog); + const payload: SaveAssessmentDialogStatePayload = { + enabled: showDialog, + telemetry, + }; + this.dispatcher.dispatchMessage({ + messageType: Messages.UserConfig.SetSaveAssessmentDialogState, + payload, + }); + } } diff --git a/src/common/messages.ts b/src/common/messages.ts index 8ac03440f62..baaf81289c8 100644 --- a/src/common/messages.ts +++ b/src/common/messages.ts @@ -69,6 +69,7 @@ export const Messages = { SetAdbLocationConfig: `${messagePrefix}/userConfig/setAdbLocationConfig`, SaveWindowBounds: `${messagePrefix}/userConfig/saveWindowBounds`, SetAutoDetectedFailuresDialogState: `${messagePrefix}/userConfig/setAutoDetectedFailuresDialogState`, + SetSaveAssessmentDialogState: `${messagePrefix}/userConfig/setSaveAssessmentDialogState`, }, Popup: { diff --git a/src/common/telemetry-data-factory.ts b/src/common/telemetry-data-factory.ts index 0a1d43f529b..57c99495009 100644 --- a/src/common/telemetry-data-factory.ts +++ b/src/common/telemetry-data-factory.ts @@ -30,6 +30,7 @@ import { SetAllUrlsPermissionTelemetryData, SettingsOpenSourceItem, SettingsOpenTelemetryData, + ShowAssessmentDialogStateTelemetryData, TabStopAutomatedFailuresInstanceCount, TabStopRequirementInstanceCount, TabStopsAutomatedResultsTelemetryData, @@ -510,4 +511,16 @@ export class TelemetryDataFactory { enabled, }; } + + public forSetShowAssessmentDialogState( + enabled: boolean, + ): ShowAssessmentDialogStateTelemetryData | undefined { + if (enabled === undefined) { + return undefined; + } + + return { + enabled, + }; + } } diff --git a/src/common/types/store-data/user-configuration-store.ts b/src/common/types/store-data/user-configuration-store.ts index b84a68bd97d..4072ef25739 100644 --- a/src/common/types/store-data/user-configuration-store.ts +++ b/src/common/types/store-data/user-configuration-store.ts @@ -24,6 +24,7 @@ export interface UserConfigurationStoreData { lastWindowBounds: Rectangle | null; showAutoDetectedFailuresDialog: boolean; + showSaveAssessmentDialog: boolean; } export interface IssueFilingServicePropertiesMap { diff --git a/src/tests/unit/tests/DetailsView/__snapshots__/details-view-content.test.tsx.snap b/src/tests/unit/tests/DetailsView/__snapshots__/details-view-content.test.tsx.snap index 46dff306394..3129aa2de58 100644 --- a/src/tests/unit/tests/DetailsView/__snapshots__/details-view-content.test.tsx.snap +++ b/src/tests/unit/tests/DetailsView/__snapshots__/details-view-content.test.tsx.snap @@ -186,6 +186,7 @@ exports[`DetailsViewContent render renders normally 1`] = ` "lastWindowBounds": null, "lastWindowState": null, "showAutoDetectedFailuresDialog": true, + "showSaveAssessmentDialog": true, } } visualizationScanResultData={ @@ -445,6 +446,7 @@ exports[`DetailsViewContent render renders normally 1`] = ` "lastWindowBounds": null, "lastWindowState": null, "showAutoDetectedFailuresDialog": true, + "showSaveAssessmentDialog": true, } } /> diff --git a/src/tests/unit/tests/DetailsView/components/__snapshots__/auto-detected-failures-dialog.test.tsx.snap b/src/tests/unit/tests/DetailsView/components/__snapshots__/auto-detected-failures-dialog.test.tsx.snap index 87775cea9ce..bb9b75df200 100644 --- a/src/tests/unit/tests/DetailsView/components/__snapshots__/auto-detected-failures-dialog.test.tsx.snap +++ b/src/tests/unit/tests/DetailsView/components/__snapshots__/auto-detected-failures-dialog.test.tsx.snap @@ -31,15 +31,36 @@ exports[`AutoDetectedFailuresDialog on dialog enabled box appears checked when " - - + + + + + + + + `; @@ -77,15 +98,36 @@ exports[`AutoDetectedFailuresDialog on dialog enabled nothing happens when check - - + + + + + + + + `; @@ -119,15 +161,36 @@ exports[`AutoDetectedFailuresDialog on dialog enabled renders when dialog is ena - - + + + + + + + + `; diff --git a/src/tests/unit/tests/DetailsView/components/__snapshots__/details-view-command-bar.test.tsx.snap b/src/tests/unit/tests/DetailsView/components/__snapshots__/details-view-command-bar.test.tsx.snap index 24e79a2c66e..a2e1f23c15e 100644 --- a/src/tests/unit/tests/DetailsView/components/__snapshots__/details-view-command-bar.test.tsx.snap +++ b/src/tests/unit/tests/DetailsView/components/__snapshots__/details-view-command-bar.test.tsx.snap @@ -1490,18 +1490,78 @@ exports[`DetailsViewCommandBar renders with report export dialog open 1`] = ` `; exports[`DetailsViewCommandBar renders with save assessment button 1`] = ` - + - Save assessment - + onClick={[Function]} + > + Save assessment + + + `; exports[`DetailsViewCommandBar renders with start assessment over dialog open 1`] = ` diff --git a/src/tests/unit/tests/DetailsView/components/__snapshots__/save-assessment-button.test.tsx.snap b/src/tests/unit/tests/DetailsView/components/__snapshots__/save-assessment-button.test.tsx.snap index 135ea0cfb74..27a6992a3cd 100644 --- a/src/tests/unit/tests/DetailsView/components/__snapshots__/save-assessment-button.test.tsx.snap +++ b/src/tests/unit/tests/DetailsView/components/__snapshots__/save-assessment-button.test.tsx.snap @@ -1,16 +1,76 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`SaveAssessmentButton should render per the snapshot 1`] = ` - + - Save assessment - + onClick={[Function]} + > + Save assessment + + + `; diff --git a/src/tests/unit/tests/DetailsView/components/details-view-command-bar.test.tsx b/src/tests/unit/tests/DetailsView/components/details-view-command-bar.test.tsx index bb4ea62b58b..a966f7c87d1 100644 --- a/src/tests/unit/tests/DetailsView/components/details-view-command-bar.test.tsx +++ b/src/tests/unit/tests/DetailsView/components/details-view-command-bar.test.tsx @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { ActionButton, IButton } from '@fluentui/react'; +import { UserConfigMessageCreator } from 'common/message-creators/user-config-message-creator'; import { NamedFC, ReactFCWithDisplayName } from 'common/react/named-fc'; import { AssessmentStoreData, @@ -8,6 +9,7 @@ import { } from 'common/types/store-data/assessment-result-data'; import { TabStoreData } from 'common/types/store-data/tab-store-data'; import { ScanMetadata } from 'common/types/store-data/unified-data-interface'; +import { UserConfigurationStoreData } from 'common/types/store-data/user-configuration-store'; import { DetailsViewActionMessageCreator } from 'DetailsView/actions/details-view-action-message-creator'; import { CommandBarProps, @@ -58,6 +60,8 @@ describe('DetailsViewCommandBar', () => { let saveAssessmentButtonFactoryMock: IMock; let loadAssessmentButtonFactoryMock: IMock; let getStartOverComponentMock: IMock<(Props: StartOverFactoryProps) => JSX.Element>; + let userConfigMessageCreatorMock: IMock; + let userConfigurationStoreData: UserConfigurationStoreData; beforeEach(() => { detailsViewActionMessageCreatorMock = Mock.ofType( @@ -76,10 +80,18 @@ describe('DetailsViewCommandBar', () => { startOverComponent = null; isCommandBarCollapsed = false; showReportExportButton = true; + userConfigMessageCreatorMock = Mock.ofType(UserConfigMessageCreator); + userConfigurationStoreData = { + showSaveAssessmentDialog: true, + } as UserConfigurationStoreData; saveAssessmentButtonPropsStub = { - deps: { detailsViewActionMessageCreator: detailsViewActionMessageCreatorMock.object }, + deps: { + detailsViewActionMessageCreator: detailsViewActionMessageCreatorMock.object, + userConfigMessageCreator: userConfigMessageCreatorMock.object, + }, download: 'download', href: 'url', + userConfigurationStoreData, }; assessmentStoreData = {} as AssessmentStoreData; diff --git a/src/tests/unit/tests/DetailsView/components/details-view-overlay/settings-panel/settings/issue-filing/issue-filing-settings.test.tsx b/src/tests/unit/tests/DetailsView/components/details-view-overlay/settings-panel/settings/issue-filing/issue-filing-settings.test.tsx index 1c17bc1e8c7..392f1c6dc88 100644 --- a/src/tests/unit/tests/DetailsView/components/details-view-overlay/settings-panel/settings/issue-filing/issue-filing-settings.test.tsx +++ b/src/tests/unit/tests/DetailsView/components/details-view-overlay/settings-panel/settings/issue-filing/issue-filing-settings.test.tsx @@ -35,6 +35,7 @@ describe('IssueFilingSettings', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; testIssueFilingServiceStub = { key: testKey, diff --git a/src/tests/unit/tests/DetailsView/components/details-view-overlay/settings-panel/settings/save-assessment-dialog/__snapshots__/save-assessment-dialog.test.tsx.snap b/src/tests/unit/tests/DetailsView/components/details-view-overlay/settings-panel/settings/save-assessment-dialog/__snapshots__/save-assessment-dialog.test.tsx.snap new file mode 100644 index 00000000000..1366eecf7dc --- /dev/null +++ b/src/tests/unit/tests/DetailsView/components/details-view-overlay/settings-panel/settings/save-assessment-dialog/__snapshots__/save-assessment-dialog.test.tsx.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SaveAssessmentDialog renders with enabled = false 1`] = ` + +`; + +exports[`SaveAssessmentDialog renders with enabled = true 1`] = ` + +`; diff --git a/src/tests/unit/tests/DetailsView/components/details-view-overlay/settings-panel/settings/save-assessment-dialog/save-assessment-dialog.test.tsx b/src/tests/unit/tests/DetailsView/components/details-view-overlay/settings-panel/settings/save-assessment-dialog/save-assessment-dialog.test.tsx new file mode 100644 index 00000000000..d92b9a162d4 --- /dev/null +++ b/src/tests/unit/tests/DetailsView/components/details-view-overlay/settings-panel/settings/save-assessment-dialog/save-assessment-dialog.test.tsx @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { Toggle } from '@fluentui/react'; +import { UserConfigMessageCreator } from 'common/message-creators/user-config-message-creator'; +import { UserConfigurationStoreData } from 'common/types/store-data/user-configuration-store'; +import { SaveAssessmentDialogSettings } from 'DetailsView/components/details-view-overlay/settings-panel/settings/save-assessment-dialog/save-assessment-dialog-settings'; +import { + SettingsDeps, + SettingsProps, +} from 'DetailsView/components/details-view-overlay/settings-panel/settings/settings-props'; +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { Mock, Times } from 'typemoq'; + +describe('SaveAssessmentDialog', () => { + const enableStates = [true, false]; + + describe('renders', () => { + it.each(enableStates)('with enabled = %s', enabled => { + const props: SettingsProps = { + deps: Mock.ofType().object, + userConfigurationStoreState: { + showSaveAssessmentDialog: enabled, + } as UserConfigurationStoreData, + featureFlagData: {}, + }; + + const wrapper = shallow(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); + }); + + describe('user interaction', () => { + it.each(enableStates)('handles toggle click, with enabled = %s', enabled => { + const userConfigMessageCreatorMock = Mock.ofType(); + const deps = { + userConfigMessageCreator: userConfigMessageCreatorMock.object, + } as SettingsDeps; + const props: SettingsProps = { + deps, + userConfigurationStoreState: { + showSaveAssessmentDialog: enabled, + } as UserConfigurationStoreData, + featureFlagData: {}, + }; + + const wrapper = shallow(); + + userConfigMessageCreatorMock + .setup(creator => creator.setSaveAssessmentDialogState(!enabled)) + .verifiable(Times.once()); + + wrapper.dive().find(Toggle).simulate('click'); + + userConfigMessageCreatorMock.verifyAll(); + }); + }); +}); diff --git a/src/tests/unit/tests/DetailsView/components/save-assessment-button.test.tsx b/src/tests/unit/tests/DetailsView/components/save-assessment-button.test.tsx index ff8bc0251a8..47854d6c196 100644 --- a/src/tests/unit/tests/DetailsView/components/save-assessment-button.test.tsx +++ b/src/tests/unit/tests/DetailsView/components/save-assessment-button.test.tsx @@ -1,47 +1,108 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { Dialog, PrimaryButton } from '@fluentui/react'; import { InsightsCommandButton } from 'common/components/controls/insights-command-button'; +import { UserConfigMessageCreator } from 'common/message-creators/user-config-message-creator'; +import { UserConfigurationStoreData } from 'common/types/store-data/user-configuration-store'; import { DetailsViewActionMessageCreator } from 'DetailsView/actions/details-view-action-message-creator'; import { SaveAssessmentButton, SaveAssessmentButtonProps, } from 'DetailsView/components/save-assessment-button'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import * as React from 'react'; import { EventStubFactory } from 'tests/unit/common/event-stub-factory'; -import { IMock, It, Mock, MockBehavior } from 'typemoq'; +import { IMock, Mock, Times } from 'typemoq'; describe('SaveAssessmentButton', () => { let propsStub: SaveAssessmentButtonProps; let detailsViewActionMessageCreatorMock: IMock; + let eventStub: any; + let userConfigMessageCreatorMock: IMock; + let userConfigurationStoreData: UserConfigurationStoreData; beforeEach(() => { - detailsViewActionMessageCreatorMock = Mock.ofType( - DetailsViewActionMessageCreator, - MockBehavior.Strict, - ); + detailsViewActionMessageCreatorMock = Mock.ofType(); + userConfigurationStoreData = { + showSaveAssessmentDialog: true, + } as UserConfigurationStoreData; + userConfigMessageCreatorMock = Mock.ofType(UserConfigMessageCreator); propsStub = { - deps: { detailsViewActionMessageCreator: detailsViewActionMessageCreatorMock.object }, + deps: { + detailsViewActionMessageCreator: detailsViewActionMessageCreatorMock.object, + userConfigMessageCreator: userConfigMessageCreatorMock.object, + }, download: 'download', href: 'url', + userConfigurationStoreData, }; + eventStub = new EventStubFactory().createMouseClickEvent(); }); - it('should render per the snapshot', () => { - const rendered = shallow(); + describe('on dialog enabled', () => { + let wrapper: ShallowWrapper; - expect(rendered.getElement()).toMatchSnapshot(); + beforeEach(() => { + wrapper = shallow(); + wrapper.find(InsightsCommandButton).simulate('click', eventStub); + }); + + it('snapshot of dialog', () => { + expect(wrapper.getElement()).toMatchSnapshot(); + }); + + it('dialog is visible', () => { + expect(wrapper.find(Dialog).props().hidden).toEqual(false); + }); + + it('dialog is hidden (dismissed) when "got it" button is clicked', () => { + wrapper.find(PrimaryButton).simulate('click'); + expect(wrapper.find(Dialog).props().hidden).toEqual(true); + }); + + it('dialog is hidden (dismissed) when onDismiss is called', () => { + wrapper.find(Dialog).prop('onDismiss')(); + expect(wrapper.find(Dialog).props().hidden).toEqual(true); + }); + + it('when "dont show again" box is clicked, set the showSaveAssessmentDialog user config state to `false`', () => { + // The "Don't show again" checkbox logic is inverted + const checkbox = wrapper.find('StyledCheckboxBase'); + // Check "Don't show again" = true + checkbox.simulate('change', null, true); + // showSaveAssessmentDialog = false ("Enable the dialog" = false) + userConfigMessageCreatorMock.verify( + x => x.setSaveAssessmentDialogState(false), + Times.atLeastOnce(), + ); + }); + + it('should call saveAssessment on click', async () => { + detailsViewActionMessageCreatorMock.verify( + x => x.saveAssessment(eventStub), + Times.atLeastOnce(), + ); + }); }); - it('should call saveAssessment on click', async () => { - const eventStub = new EventStubFactory().createMouseClickEvent() as any; - const rendered = shallow(); - const button = rendered.find(InsightsCommandButton); + describe('on dialog disabled', () => { + let wrapper: ShallowWrapper; - detailsViewActionMessageCreatorMock.setup(m => m.saveAssessment(It.isAny())).verifiable(); + beforeEach(() => { + propsStub.userConfigurationStoreData.showSaveAssessmentDialog = false; + wrapper = shallow(); + wrapper.find(InsightsCommandButton).simulate('click', eventStub); + }); - await button.simulate('click', eventStub); + it('saves assessment without dialog (dialog is hidden)', () => { + expect(wrapper.find(Dialog).props().hidden).toEqual(true); + }); - detailsViewActionMessageCreatorMock.verifyAll(); + it('should call saveAssessment on click', async () => { + detailsViewActionMessageCreatorMock.verify( + x => x.saveAssessment(eventStub), + Times.atLeastOnce(), + ); + }); }); }); diff --git a/src/tests/unit/tests/DetailsView/components/should-show-report-export-button.test.ts b/src/tests/unit/tests/DetailsView/components/should-show-report-export-button.test.ts index 8d716ab132f..99d3b1eaabe 100644 --- a/src/tests/unit/tests/DetailsView/components/should-show-report-export-button.test.ts +++ b/src/tests/unit/tests/DetailsView/components/should-show-report-export-button.test.ts @@ -4,6 +4,7 @@ import { VisualizationConfiguration } from 'common/configs/visualization-configu import { VisualizationConfigurationFactory } from 'common/configs/visualization-configuration-factory'; import { TabStoreData } from 'common/types/store-data/tab-store-data'; import { UnifiedScanResultStoreData } from 'common/types/store-data/unified-data-interface'; +import { UserConfigurationStoreData } from 'common/types/store-data/user-configuration-store'; import { VisualizationStoreData } from 'common/types/store-data/visualization-store-data'; import { VisualizationType } from 'common/types/visualization-type'; import { DetailsViewCommandBarProps } from 'DetailsView/components/details-view-command-bar'; @@ -20,6 +21,7 @@ describe('ShouldShowReportExportButton', () => { const visualizationStoreData = { tests: {} } as VisualizationStoreData; const unifiedScanResultStoreData = {} as UnifiedScanResultStoreData; + const userConfigurationStoreData = {} as UserConfigurationStoreData; const selectedTest = -1 as VisualizationType; @@ -49,6 +51,7 @@ describe('ShouldShowReportExportButton', () => { scanMetadata: null, narrowModeStatus: null, tabStopRequirementData: null, + userConfigurationStoreData, } as DetailsViewCommandBarProps; } diff --git a/src/tests/unit/tests/DetailsView/details-view-content.test.tsx b/src/tests/unit/tests/DetailsView/details-view-content.test.tsx index eab96524274..71479a01bbb 100644 --- a/src/tests/unit/tests/DetailsView/details-view-content.test.tsx +++ b/src/tests/unit/tests/DetailsView/details-view-content.test.tsx @@ -163,6 +163,7 @@ describe(DetailsViewContent.displayName, () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; const unifiedScanResultStoreData: UnifiedScanResultStoreData = { diff --git a/src/tests/unit/tests/background/get-persisted-data.test.ts b/src/tests/unit/tests/background/get-persisted-data.test.ts index 83e380e8eb7..93804275e39 100644 --- a/src/tests/unit/tests/background/get-persisted-data.test.ts +++ b/src/tests/unit/tests/background/get-persisted-data.test.ts @@ -46,6 +46,7 @@ describe('GetPersistedDataTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; installationData = { id: 'test-id', diff --git a/src/tests/unit/tests/background/global-action-creators/user-configuration-action-creator.test.ts b/src/tests/unit/tests/background/global-action-creators/user-configuration-action-creator.test.ts index d28bf8c65a7..e6c45e21ddb 100644 --- a/src/tests/unit/tests/background/global-action-creators/user-configuration-action-creator.test.ts +++ b/src/tests/unit/tests/background/global-action-creators/user-configuration-action-creator.test.ts @@ -213,6 +213,33 @@ describe('UserConfigurationActionCreator', () => { ); }); + it('should publish telemetry and invoke the corresponding action in response to SetSaveAssessmentDialogState message', async () => { + const expectedDialogState = { enabled: false }; + const expectedPayload = expectedDialogState as BaseActionPayload; + + const dialogStateConfigMock = createAsyncActionMock(expectedDialogState); + const actionsMock = createActionsMock( + 'setSaveAssessmentDialogState', + dialogStateConfigMock.object, + ); + const testSubject = new UserConfigurationActionCreator( + actionsMock.object, + telemetryEventHandlerMock.object, + ); + + await testSubject.setSaveAssessmentDialogState(expectedDialogState); + + dialogStateConfigMock.verifyAll(); + telemetryEventHandlerMock.verify( + handler => + handler.publishTelemetry( + TelemetryEvents.SET_SAVE_ASSESSMENT_DIALOG_STATE, + expectedPayload, + ), + Times.once(), + ); + }); + function createActionsMock( actionName: ActionName, action: UserConfigurationActions[ActionName], diff --git a/src/tests/unit/tests/background/keyboard-shortcut-handler.test.ts b/src/tests/unit/tests/background/keyboard-shortcut-handler.test.ts index a83a76d1a96..869d57f395d 100644 --- a/src/tests/unit/tests/background/keyboard-shortcut-handler.test.ts +++ b/src/tests/unit/tests/background/keyboard-shortcut-handler.test.ts @@ -103,6 +103,7 @@ describe('KeyboardShortcutHandler', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; }); diff --git a/src/tests/unit/tests/background/stores/global/user-configuration-store.test.ts b/src/tests/unit/tests/background/stores/global/user-configuration-store.test.ts index eec4a05484c..73d7cd94234 100644 --- a/src/tests/unit/tests/background/stores/global/user-configuration-store.test.ts +++ b/src/tests/unit/tests/background/stores/global/user-configuration-store.test.ts @@ -12,6 +12,7 @@ import { import { UserConfigurationActions } from 'background/actions/user-configuration-actions'; import { IndexedDBDataKeys } from 'background/IndexedDBDataKeys'; import { UserConfigurationStore } from 'background/stores/global/user-configuration-store'; +import { ShowAssessmentDialogStateTelemetryData } from 'common/extension-telemetry-events'; import { WindowState } from 'electron/flux/types/window-state'; import { cloneDeep } from 'lodash'; import { failTestOnErrorLogger } from 'tests/unit/common/fail-test-on-error-logger'; @@ -43,6 +44,7 @@ describe('UserConfigurationStoreTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; defaultStoreData = { enableTelemetry: false, @@ -55,6 +57,7 @@ describe('UserConfigurationStoreTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; indexDbStrictMock = Mock.ofType(); }); @@ -103,6 +106,7 @@ describe('UserConfigurationStoreTest', () => { enableHighContrast: true, adbLocation: 'test', showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; const expected: UserConfigurationStoreData = { bugService: 'none', @@ -211,6 +215,7 @@ describe('UserConfigurationStoreTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; const expectedState: UserConfigurationStoreData = { @@ -224,6 +229,7 @@ describe('UserConfigurationStoreTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; indexDbStrictMock @@ -265,6 +271,7 @@ describe('UserConfigurationStoreTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; const setHighContrastData: SetHighContrastModePayload = { @@ -282,6 +289,7 @@ describe('UserConfigurationStoreTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; indexDbStrictMock @@ -323,6 +331,7 @@ describe('UserConfigurationStoreTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; const setNativeHighContrastData: SetNativeHighContrastModePayload = { @@ -340,6 +349,7 @@ describe('UserConfigurationStoreTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; indexDbStrictMock @@ -372,6 +382,7 @@ describe('UserConfigurationStoreTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; const setIssueFilingServiceData: SetIssueFilingServicePayload = { @@ -418,6 +429,7 @@ describe('UserConfigurationStoreTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; const setIssueFilingServicePropertyData: SetIssueFilingServicePropertyPayload = { @@ -519,6 +531,30 @@ describe('UserConfigurationStoreTest', () => { .testListenerToBeCalledOnce(cloneDeep(initialStoreData), expectedState); }); + test('setSaveAssessmentDialogState', async () => { + const storeTester = createStoreToTestAction('setSaveAssessmentDialogState'); + const showSaveAssessmentDialog = false; + const payload: ShowAssessmentDialogStateTelemetryData = { + enabled: showSaveAssessmentDialog, + }; + const expectedState: UserConfigurationStoreData = { + ...initialStoreData, + showSaveAssessmentDialog, + }; + + indexDbStrictMock + .setup(indexDb => + indexDb.setItem(IndexedDBDataKeys.userConfiguration, It.isValue(expectedState)), + ) + .returns(() => Promise.resolve(true)) + .verifiable(Times.once()); + + await storeTester + .withActionParam(payload) + .withPostListenerMock(indexDbStrictMock) + .testListenerToBeCalledOnce(cloneDeep(initialStoreData), expectedState); + }); + test.each(['normal', 'maximized', 'full-screen'])( 'saveLastWindowBounds windowState:$windowState', async windowState => { @@ -540,6 +576,7 @@ describe('UserConfigurationStoreTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; const expectedState: UserConfigurationStoreData = { diff --git a/src/tests/unit/tests/common/components/cards/card-footer-instance-action-buttons.test.tsx b/src/tests/unit/tests/common/components/cards/card-footer-instance-action-buttons.test.tsx index 13fcc008afc..e45769185e1 100644 --- a/src/tests/unit/tests/common/components/cards/card-footer-instance-action-buttons.test.tsx +++ b/src/tests/unit/tests/common/components/cards/card-footer-instance-action-buttons.test.tsx @@ -103,6 +103,7 @@ describe(CardFooterInstanceActionButtons, () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; issueFilingServiceProviderMock diff --git a/src/tests/unit/tests/common/components/cards/card-kebab-menu-button.test.tsx b/src/tests/unit/tests/common/components/cards/card-kebab-menu-button.test.tsx index a146ebcbc96..dc8f1267ed9 100644 --- a/src/tests/unit/tests/common/components/cards/card-kebab-menu-button.test.tsx +++ b/src/tests/unit/tests/common/components/cards/card-kebab-menu-button.test.tsx @@ -116,6 +116,7 @@ describe('CardKebabMenuButtonTest', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; issueFilingServiceProviderMock diff --git a/src/tests/unit/tests/popup/components/popup-view.test.tsx b/src/tests/unit/tests/popup/components/popup-view.test.tsx index aa1873d8f60..4368a3d779d 100644 --- a/src/tests/unit/tests/popup/components/popup-view.test.tsx +++ b/src/tests/unit/tests/popup/components/popup-view.test.tsx @@ -83,6 +83,7 @@ describe('PopupView', () => { lastWindowState: null, lastWindowBounds: null, showAutoDetectedFailuresDialog: true, + showSaveAssessmentDialog: true, }; beforeEach(() => { diff --git a/tsconfig.strictNullChecks.json b/tsconfig.strictNullChecks.json index c0d627d9354..f2fe9e88f9b 100644 --- a/tsconfig.strictNullChecks.json +++ b/tsconfig.strictNullChecks.json @@ -440,6 +440,7 @@ "./src/issue-filing/services/github/create-github-issue-filing-url.ts", "./src/issue-filing/services/github/github-issue-filing-settings.tsx", "./src/issue-filing/services/github/github-url-rectifier.ts", + "./src/issue-filing/types/settings-form-props.ts", "./src/issue-filing/unified-result-to-issue-filing-data.ts", "./src/popup/components/file-url-unsupported-message-panel.tsx", "./src/popup/components/header.tsx", @@ -574,6 +575,7 @@ ], "include": [ "src/DetailsView/actions/**/*", + "src/DetailsView/components/details-view-overlay/settings-panel/**/*", "src/Devtools/**/*", "src/ad-hoc-visualizations/calculated-tab-stops/**/*", "src/assessments/common/**/*", @@ -605,6 +607,7 @@ "src/electron/main/**/*", "src/electron/platform/android/setup/state-machine/**/*", "src/electron/resources/**/*", + "src/electron/settings/**/*", "src/electron/views/automated-checks/**/*", "src/electron/window-management/**/*", "src/fast-pass/**/*", @@ -622,6 +625,7 @@ "src/tests/miscellaneous/**/*", "src/tests/unit/mock-helpers/**/*", "src/tests/unit/stubs/**/*", + "src/tests/unit/tests/common/icons/**/*", "src/tests/unit/tests/reports/package/scans/**/*", "src/tests/unit/tests/test-resources/**/*", "src/types/**/*",