Bug 1956042 - Support multiple instances of SingleSelect on the same aboutwelcome screen r=omc-reviewers,jprickett,mviar" for causing failures at browser_aboutwelcome_multiselect.js.

This commit is contained in:
hanna.alemu
2025-05-05 14:31:10 +03:00
committed by abutkovits@mozilla.com
parent bf1c7aa6ae
commit aa6e7c7b9f
7 changed files with 408 additions and 116 deletions

View File

@@ -155,8 +155,13 @@ export const ContentTiles = props => {
content={{ tiles: tile }}
activeTheme={props.activeTheme}
handleAction={props.handleAction}
activeSingleSelect={props.activeSingleSelect}
setActiveSingleSelect={props.setActiveSingleSelect}
activeSingleSelectSelections={
props.activeSingleSelectSelections
}
setActiveSingleSelectSelection={
props.setActiveSingleSelectSelection
}
singleSelectId={`single-select-${index}`}
/>
)}
{tile.type === "mobile_downloads" && tile.data && (

View File

@@ -199,11 +199,12 @@ export const MultiStageAboutWelcome = props => {
// multi select screen.
const [activeMultiSelects, setActiveMultiSelects] = useState({});
// Save the active single select state for each screen as string value keyed
// Save the active single select state for each screen as an object keyed
// by screen id. Similar to above, this allows us to remember the state of
// each screen's single select picker when navigating back and forth between
// screens.
const [activeSingleSelects, setActiveSingleSelects] = useState({});
// screens, and allows us to have multiple single selects on a screen.
const [activeSingleSelectSelections, setActiveSingleSelectSelections] =
useState({});
// Get the active theme so the rendering code can make it selected
// by default.
@@ -283,14 +284,25 @@ export const MultiStageAboutWelcome = props => {
});
};
const setActiveSingleSelect = valueOrFn =>
setActiveSingleSelects(prevState => ({
...prevState,
[currentScreen.id]:
typeof valueOrFn === "function"
? valueOrFn(prevState[currentScreen.id])
: valueOrFn,
}));
const setActiveSingleSelectSelection = (
valueOrFn,
singleSelectId
) => {
setActiveSingleSelectSelections(prevState => {
const currentScreenSelections = prevState[currentScreen.id] || {};
return {
...prevState,
[currentScreen.id]: {
...currentScreenSelections,
[singleSelectId]:
typeof valueOrFn === "function"
? valueOrFn(prevState[currentScreen.id])
: valueOrFn,
},
};
});
};
return index === order ? (
<WelcomeScreen
@@ -316,8 +328,10 @@ export const MultiStageAboutWelcome = props => {
activeMultiSelect={activeMultiSelects[currentScreen.id]}
setActiveMultiSelect={setActiveMultiSelect}
autoAdvance={currentScreen.auto_advance}
activeSingleSelect={activeSingleSelects[currentScreen.id]}
setActiveSingleSelect={setActiveSingleSelect}
activeSingleSelectSelections={
activeSingleSelectSelections[currentScreen.id]
}
setActiveSingleSelectSelection={setActiveSingleSelectSelection}
negotiatedLanguage={negotiatedLanguage}
langPackInstallPhase={langPackInstallPhase}
forceHideStepsIndicator={currentScreen.force_hide_steps_indicator}
@@ -482,8 +496,63 @@ export class WelcomeScreen extends React.PureComponent {
return AboutWelcomeUtils.handleUserAction({ type, data });
}
logTelemetry({ value, event, source, props }) {
AboutWelcomeUtils.sendActionTelemetry(props.messageId, source, event.name);
// Send additional telemetry if a messaging surface like feature callout is
// dismissed via the dismiss button. Other causes of dismissal will be
// handled separately by the messaging surface's own code.
if (value === "dismiss_button" && !event.name) {
AboutWelcomeUtils.sendDismissTelemetry(props.messageId, source);
}
}
async handleMigrationIfNeeded(action, props) {
const hasMigrate = a =>
a.type === "SHOW_MIGRATION_WIZARD" ||
(a.type === "MULTI_ACTION" && a.data?.actions?.some(hasMigrate));
if (hasMigrate(action)) {
await window.AWWaitForMigrationClose();
AboutWelcomeUtils.sendActionTelemetry(props.messageId, "migrate_close");
}
}
applyThemeIfNeeded(action, event) {
if (!action.theme) {
return;
}
const themeToUse =
action.theme === "<event>"
? event.currentTarget.value
: this.props.initialTheme || action.theme;
this.props.setActiveTheme(themeToUse);
window.AWSelectTheme(themeToUse);
}
handlePickerAction(value) {
const tileGroups = Array.isArray(this.props.content.tiles)
? this.props.content.tiles
: [this.props.content.tiles];
for (const tile of tileGroups) {
if (!tile?.data) {
continue;
}
for (const opt of tile.data) {
if (opt.id === value) {
AboutWelcomeUtils.handleUserAction(opt.action);
return;
}
}
}
}
async handleAction(event) {
let { props } = this;
const { props } = this;
const value =
event.currentTarget.value ?? event.currentTarget.getAttribute("value");
const source = event.source || value;
@@ -496,20 +565,27 @@ export class WelcomeScreen extends React.PureComponent {
targetContent = { action: event.action };
}
if (!(targetContent && targetContent.action)) {
if (!targetContent) {
return;
}
// Send telemetry before waiting on actions
AboutWelcomeUtils.sendActionTelemetry(props.messageId, source, event.name);
// Send additional telemetry if a messaging surface like feature callout is
// dismissed via the dismiss button. Other causes of dismissal will be
// handled separately by the messaging surface's own code.
if (value === "dismiss_button" && !event.name) {
AboutWelcomeUtils.sendDismissTelemetry(props.messageId, source);
let action;
if (Array.isArray(targetContent)) {
for (const tile of targetContent) {
const matchedTile = tile.data.find(t => t.id === value);
if (matchedTile?.action) {
action = matchedTile.action;
break;
}
}
} else if (!targetContent.action) {
return;
} else {
action = targetContent.action;
}
// Send telemetry before waiting on actions
this.logTelemetry({ value, event, source, props });
let { action } = targetContent;
action = JSON.parse(JSON.stringify(action));
if (action.collectSelect) {
@@ -543,32 +619,14 @@ export class WelcomeScreen extends React.PureComponent {
AboutWelcomeUtils.handleUserAction(action);
}
// Wait until migration closes to complete the action
const hasMigrate = a =>
a.type === "SHOW_MIGRATION_WIZARD" ||
(a.type === "MULTI_ACTION" && a.data?.actions?.some(hasMigrate));
if (hasMigrate(action)) {
await window.AWWaitForMigrationClose();
AboutWelcomeUtils.sendActionTelemetry(props.messageId, "migrate_close");
}
await this.handleMigrationIfNeeded(action, props);
}
// A special tiles.action.theme value indicates we should use the event's value vs provided value.
if (action.theme) {
let themeToUse =
action.theme === "<event>"
? event.currentTarget.value
: this.props.initialTheme || action.theme;
this.props.setActiveTheme(themeToUse);
window.AWSelectTheme(themeToUse);
}
this.applyThemeIfNeeded(action, event);
if (action.picker) {
let options = props.content.tiles.data;
options.forEach(opt => {
if (opt.id === value) {
AboutWelcomeUtils.handleUserAction(opt.action);
}
});
this.handlePickerAction(value);
}
// If the action has persistActiveTheme: true, we set the initial theme to the currently active theme
@@ -701,8 +759,10 @@ export class WelcomeScreen extends React.PureComponent {
setScreenMultiSelects={this.props.setScreenMultiSelects}
activeMultiSelect={this.props.activeMultiSelect}
setActiveMultiSelect={this.props.setActiveMultiSelect}
activeSingleSelect={this.props.activeSingleSelect}
setActiveSingleSelect={this.props.setActiveSingleSelect}
activeSingleSelectSelections={this.props.activeSingleSelectSelections}
setActiveSingleSelectSelection={
this.props.setActiveSingleSelectSelection
}
totalNumberOfScreens={this.props.totalNumberOfScreens}
appAndSystemLocaleInfo={this.props.appAndSystemLocaleInfo}
negotiatedLanguage={this.props.negotiatedLanguage}

View File

@@ -61,8 +61,8 @@ export const MultiStageProtonScreen = props => {
setScreenMultiSelects={props.setScreenMultiSelects}
activeMultiSelect={props.activeMultiSelect}
setActiveMultiSelect={props.setActiveMultiSelect}
activeSingleSelect={props.activeSingleSelect}
setActiveSingleSelect={props.setActiveSingleSelect}
activeSingleSelectSelections={props.activeSingleSelectSelections}
setActiveSingleSelectSelection={props.setActiveSingleSelectSelection}
totalNumberOfScreens={props.totalNumberOfScreens}
handleAction={props.handleAction}
isFirstScreen={props.isFirstScreen}

View File

@@ -9,11 +9,12 @@ import { AboutWelcomeUtils } from "../lib/aboutwelcome-utils.mjs";
// This component was formerly "Themes" and continues to support theme
export const SingleSelect = ({
activeSingleSelect,
activeSingleSelectSelections = {}, // This now holds all active selections keyed by `singleSelectId`
activeTheme,
content,
handleAction,
setActiveSingleSelect,
setActiveSingleSelectSelection,
singleSelectId,
}) => {
const category = content.tiles?.category?.type || content.tiles?.type;
const isSingleSelect = category === "single-select";
@@ -48,10 +49,12 @@ export const SingleSelect = ({
// When screen renders for first time or user navigates back, update state to
// check default option.
useEffect(() => {
if (isSingleSelect && !activeSingleSelect) {
if (isSingleSelect && !activeSingleSelectSelections[singleSelectId]) {
let newActiveSingleSelect =
content.tiles?.selected || content.tiles?.data[0].id;
setActiveSingleSelect(newActiveSingleSelect);
setActiveSingleSelectSelection(newActiveSingleSelect, singleSelectId);
let selectedTile = content.tiles?.data.find(
opt => opt.id === newActiveSingleSelect
);
@@ -65,7 +68,7 @@ export const SingleSelect = ({
handleAction({ currentTarget: { value: selectedTile.id } });
}
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
}, [activeSingleSelectSelections]); // eslint-disable-line react-hooks/exhaustive-deps
const CONFIGURABLE_STYLES = [
"background",
@@ -79,7 +82,7 @@ export const SingleSelect = ({
];
return (
<div className="tiles-single-select-container">
<div className={`tiles-single-select-container`}>
<div>
<fieldset className={`tiles-single-select-section ${category}`}>
<Localized text={content.subtitle}>
@@ -103,12 +106,13 @@ export const SingleSelect = ({
}
const selected =
(theme && theme === activeTheme) ||
(isSingleSelect && activeSingleSelect === value);
(isSingleSelect &&
activeSingleSelectSelections[singleSelectId] === value);
const valOrObj = val => (typeof val === "object" ? val : {});
const handleClick = evt => {
if (isSingleSelect) {
setActiveSingleSelect(value);
setActiveSingleSelectSelection(value, singleSelectId); // Update selection for the specific component
}
handleAction(evt);
};

View File

@@ -316,11 +316,11 @@ const MultiStageAboutWelcome = props => {
// multi select screen.
const [activeMultiSelects, setActiveMultiSelects] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
// Save the active single select state for each screen as string value keyed
// Save the active single select state for each screen as an object keyed
// by screen id. Similar to above, this allows us to remember the state of
// each screen's single select picker when navigating back and forth between
// screens.
const [activeSingleSelects, setActiveSingleSelects] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
// screens, and allows us to have multiple single selects on a screen.
const [activeSingleSelectSelections, setActiveSingleSelectSelections] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
// Get the active theme so the rendering code can make it selected
// by default.
@@ -382,10 +382,18 @@ const MultiStageAboutWelcome = props => {
};
});
};
const setActiveSingleSelect = valueOrFn => setActiveSingleSelects(prevState => ({
...prevState,
[currentScreen.id]: typeof valueOrFn === "function" ? valueOrFn(prevState[currentScreen.id]) : valueOrFn
}));
const setActiveSingleSelectSelection = (valueOrFn, singleSelectId) => {
setActiveSingleSelectSelections(prevState => {
const currentScreenSelections = prevState[currentScreen.id] || {};
return {
...prevState,
[currentScreen.id]: {
...currentScreenSelections,
[singleSelectId]: typeof valueOrFn === "function" ? valueOrFn(prevState[currentScreen.id]) : valueOrFn
}
};
});
};
return index === order ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(WelcomeScreen, {
key: currentScreen.id + order,
id: currentScreen.id,
@@ -409,8 +417,8 @@ const MultiStageAboutWelcome = props => {
activeMultiSelect: activeMultiSelects[currentScreen.id],
setActiveMultiSelect: setActiveMultiSelect,
autoAdvance: currentScreen.auto_advance,
activeSingleSelect: activeSingleSelects[currentScreen.id],
setActiveSingleSelect: setActiveSingleSelect,
activeSingleSelectSelections: activeSingleSelectSelections[currentScreen.id],
setActiveSingleSelectSelection: setActiveSingleSelectSelection,
negotiatedLanguage: negotiatedLanguage,
langPackInstallPhase: langPackInstallPhase,
forceHideStepsIndicator: currentScreen.force_hide_steps_indicator,
@@ -552,8 +560,52 @@ class WelcomeScreen extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
data
});
}
logTelemetry({
value,
event,
source,
props
}) {
_lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_2__.AboutWelcomeUtils.sendActionTelemetry(props.messageId, source, event.name);
// Send additional telemetry if a messaging surface like feature callout is
// dismissed via the dismiss button. Other causes of dismissal will be
// handled separately by the messaging surface's own code.
if (value === "dismiss_button" && !event.name) {
_lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_2__.AboutWelcomeUtils.sendDismissTelemetry(props.messageId, source);
}
}
async handleMigrationIfNeeded(action, props) {
const hasMigrate = a => a.type === "SHOW_MIGRATION_WIZARD" || a.type === "MULTI_ACTION" && a.data?.actions?.some(hasMigrate);
if (hasMigrate(action)) {
await window.AWWaitForMigrationClose();
_lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_2__.AboutWelcomeUtils.sendActionTelemetry(props.messageId, "migrate_close");
}
}
applyThemeIfNeeded(action, event) {
if (!action.theme) {
return;
}
const themeToUse = action.theme === "<event>" ? event.currentTarget.value : this.props.initialTheme || action.theme;
this.props.setActiveTheme(themeToUse);
window.AWSelectTheme(themeToUse);
}
handlePickerAction(value) {
const tileGroups = Array.isArray(this.props.content.tiles) ? this.props.content.tiles : [this.props.content.tiles];
for (const tile of tileGroups) {
if (!tile?.data) {
continue;
}
for (const opt of tile.data) {
if (opt.id === value) {
_lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_2__.AboutWelcomeUtils.handleUserAction(opt.action);
return;
}
}
}
}
async handleAction(event) {
let {
const {
props
} = this;
const value = event.currentTarget.value ?? event.currentTarget.getAttribute("value");
@@ -564,21 +616,30 @@ class WelcomeScreen extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
action: event.action
};
}
if (!(targetContent && targetContent.action)) {
if (!targetContent) {
return;
}
// Send telemetry before waiting on actions
_lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_2__.AboutWelcomeUtils.sendActionTelemetry(props.messageId, source, event.name);
// Send additional telemetry if a messaging surface like feature callout is
// dismissed via the dismiss button. Other causes of dismissal will be
// handled separately by the messaging surface's own code.
if (value === "dismiss_button" && !event.name) {
_lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_2__.AboutWelcomeUtils.sendDismissTelemetry(props.messageId, source);
let action;
if (Array.isArray(targetContent)) {
for (const tile of targetContent) {
const matchedTile = tile.data.find(t => t.id === value);
if (matchedTile?.action) {
action = matchedTile.action;
break;
}
}
} else if (!targetContent.action) {
return;
} else {
action = targetContent.action;
}
let {
action
} = targetContent;
// Send telemetry before waiting on actions
this.logTelemetry({
value,
event,
source,
props
});
action = JSON.parse(JSON.stringify(action));
if (action.collectSelect) {
this.setMultiSelectActions(action);
@@ -607,26 +668,13 @@ class WelcomeScreen extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
_lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_2__.AboutWelcomeUtils.handleUserAction(action);
}
// Wait until migration closes to complete the action
const hasMigrate = a => a.type === "SHOW_MIGRATION_WIZARD" || a.type === "MULTI_ACTION" && a.data?.actions?.some(hasMigrate);
if (hasMigrate(action)) {
await window.AWWaitForMigrationClose();
_lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_2__.AboutWelcomeUtils.sendActionTelemetry(props.messageId, "migrate_close");
}
await this.handleMigrationIfNeeded(action, props);
}
// A special tiles.action.theme value indicates we should use the event's value vs provided value.
if (action.theme) {
let themeToUse = action.theme === "<event>" ? event.currentTarget.value : this.props.initialTheme || action.theme;
this.props.setActiveTheme(themeToUse);
window.AWSelectTheme(themeToUse);
}
this.applyThemeIfNeeded(action, event);
if (action.picker) {
let options = props.content.tiles.data;
options.forEach(opt => {
if (opt.id === value) {
_lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_2__.AboutWelcomeUtils.handleUserAction(opt.action);
}
});
this.handlePickerAction(value);
}
// If the action has persistActiveTheme: true, we set the initial theme to the currently active theme
@@ -740,8 +788,8 @@ class WelcomeScreen extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
setScreenMultiSelects: this.props.setScreenMultiSelects,
activeMultiSelect: this.props.activeMultiSelect,
setActiveMultiSelect: this.props.setActiveMultiSelect,
activeSingleSelect: this.props.activeSingleSelect,
setActiveSingleSelect: this.props.setActiveSingleSelect,
activeSingleSelectSelections: this.props.activeSingleSelectSelections,
setActiveSingleSelectSelection: this.props.setActiveSingleSelectSelection,
totalNumberOfScreens: this.props.totalNumberOfScreens,
appAndSystemLocaleInfo: this.props.appAndSystemLocaleInfo,
negotiatedLanguage: this.props.negotiatedLanguage,
@@ -958,8 +1006,8 @@ const MultiStageProtonScreen = props => {
setScreenMultiSelects: props.setScreenMultiSelects,
activeMultiSelect: props.activeMultiSelect,
setActiveMultiSelect: props.setActiveMultiSelect,
activeSingleSelect: props.activeSingleSelect,
setActiveSingleSelect: props.setActiveSingleSelect,
activeSingleSelectSelections: props.activeSingleSelectSelections,
setActiveSingleSelectSelection: props.setActiveSingleSelectSelection,
totalNumberOfScreens: props.totalNumberOfScreens,
handleAction: props.handleAction,
isFirstScreen: props.isFirstScreen,
@@ -2229,8 +2277,9 @@ const ContentTiles = props => {
},
activeTheme: props.activeTheme,
handleAction: props.handleAction,
activeSingleSelect: props.activeSingleSelect,
setActiveSingleSelect: props.setActiveSingleSelect
activeSingleSelectSelections: props.activeSingleSelectSelections,
setActiveSingleSelectSelection: props.setActiveSingleSelectSelection,
singleSelectId: `single-select-${index}`
}), tile.type === "mobile_downloads" && tile.data && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MobileDownloads__WEBPACK_IMPORTED_MODULE_4__.MobileDownloads, {
data: tile.data,
handleAction: props.handleAction
@@ -2530,11 +2579,13 @@ __webpack_require__.r(__webpack_exports__);
// This component was formerly "Themes" and continues to support theme
const SingleSelect = ({
activeSingleSelect,
activeSingleSelectSelections = {},
// This now holds all active selections keyed by `singleSelectId`
activeTheme,
content,
handleAction,
setActiveSingleSelect
setActiveSingleSelectSelection,
singleSelectId
}) => {
const category = content.tiles?.category?.type || content.tiles?.type;
const isSingleSelect = category === "single-select";
@@ -2561,9 +2612,9 @@ const SingleSelect = ({
// When screen renders for first time or user navigates back, update state to
// check default option.
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
if (isSingleSelect && !activeSingleSelect) {
if (isSingleSelect && !activeSingleSelectSelections[singleSelectId]) {
let newActiveSingleSelect = content.tiles?.selected || content.tiles?.data[0].id;
setActiveSingleSelect(newActiveSingleSelect);
setActiveSingleSelectSelection(newActiveSingleSelect, singleSelectId);
let selectedTile = content.tiles?.data.find(opt => opt.id === newActiveSingleSelect);
// If applicable, automatically trigger the action for the default
// selected tile.
@@ -2575,11 +2626,11 @@ const SingleSelect = ({
});
}
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
}, [activeSingleSelectSelections]); // eslint-disable-line react-hooks/exhaustive-deps
const CONFIGURABLE_STYLES = ["background", "borderRadius", "height", "marginBlock", "marginInline", "paddingBlock", "paddingInline", "width"];
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "tiles-single-select-container"
className: `tiles-single-select-container`
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("fieldset", {
className: `tiles-single-select-section ${category}`
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, {
@@ -2601,11 +2652,11 @@ const SingleSelect = ({
if (!isSingleSelect) {
inputName = category === "theme" ? "theme" : id; // unique names per item are currently used in the wallpaper picker
}
const selected = theme && theme === activeTheme || isSingleSelect && activeSingleSelect === value;
const selected = theme && theme === activeTheme || isSingleSelect && activeSingleSelectSelections[singleSelectId] === value;
const valOrObj = val => typeof val === "object" ? val : {};
const handleClick = evt => {
if (isSingleSelect) {
setActiveSingleSelect(value);
setActiveSingleSelectSelection(value, singleSelectId); // Update selection for the specific component
}
handleAction(evt);
};

View File

@@ -11,7 +11,7 @@ describe("ContentTiles component", () => {
let wrapper;
let handleAction;
let setActiveMultiSelect;
let setActiveSingleSelect;
let setActiveSingleSelectSelection;
let globals;
const CHECKLIST_TILE = {
@@ -78,7 +78,7 @@ describe("ContentTiles component", () => {
sandbox = sinon.createSandbox();
handleAction = sandbox.stub();
setActiveMultiSelect = sandbox.stub();
setActiveSingleSelect = sandbox.stub();
setActiveSingleSelectSelection = sandbox.stub();
globals = new GlobalOverrider();
globals.set({
AWSendToDeviceEmailsSupported: () => Promise.resolve(),
@@ -434,7 +434,7 @@ describe("ContentTiles component", () => {
handleAction={() => {}}
activeMultiSelect={null}
setActiveMultiSelect={setActiveMultiSelect}
setActiveSingleSelect={setActiveSingleSelect}
setActiveSingleSelectSelection={setActiveSingleSelectSelection}
/>
);
@@ -655,4 +655,140 @@ describe("ContentTiles component", () => {
wrapper.unmount();
});
it("should select defaults of single select tiles independently of one another", () => {
const SINGLE_SELECT_1 = {
type: "single-select",
selected: "test1",
data: [
{
id: "test1",
label: {
raw: "test1 label",
},
},
{
defaultValue: true,
id: "test2",
label: {
raw: "test2 label",
},
},
],
};
const SINGLE_SELECT_2 = {
type: "single-select",
selected: "test4",
data: [
{
id: "test3",
label: {
raw: "test3 label",
},
},
{
defaultValue: true,
id: "test4",
label: {
raw: "test4 label",
},
},
],
};
const content = { tiles: [SINGLE_SELECT_1, SINGLE_SELECT_2] };
wrapper = mount(
<ContentTiles
content={content}
setActiveSingleSelectSelection={setActiveSingleSelectSelection}
handleAction={handleAction}
/>
);
wrapper.update();
sinon.assert.calledWithExactly(
setActiveSingleSelectSelection.getCall(0),
"test1",
"single-select-0"
);
sinon.assert.calledWithExactly(
setActiveSingleSelectSelection.getCall(1),
"test4",
"single-select-1"
);
wrapper.unmount();
});
it("should handle interactions with multiple single select tiles independently of one another", () => {
const SINGLE_SELECT_1 = {
type: "single-select",
selected: "test1",
data: [
{
id: "test1",
label: {
raw: "test1 label",
},
},
{
defaultValue: true,
id: "test2",
label: {
raw: "test2 label",
},
},
],
};
const SINGLE_SELECT_2 = {
type: "single-select",
selected: "test4",
data: [
{
id: "test3",
label: {
raw: "test3 label",
},
},
{
defaultValue: true,
id: "test4",
label: {
raw: "test4 label",
},
},
],
};
const content = { tiles: [SINGLE_SELECT_1, SINGLE_SELECT_2] };
wrapper = mount(
<ContentTiles
content={content}
setActiveSingleSelectSelection={setActiveSingleSelectSelection}
handleAction={handleAction}
/>
);
wrapper.update();
const tile2 = wrapper.find('input[value="test2"]');
tile2.simulate("click");
sinon.assert.calledWithExactly(
setActiveSingleSelectSelection.getCall(2),
"test2",
"single-select-0"
);
const tile3 = wrapper.find('input[value="test3"]');
tile3.simulate("click");
sinon.assert.calledWithExactly(
setActiveSingleSelectSelection.getCall(3),
"test3",
"single-select-1"
);
});
});

View File

@@ -236,6 +236,7 @@ describe("MultiStageAboutWelcome module", () => {
totalNumberOfScreens: 1,
setScreenMultiSelects: sandbox.stub(),
setActiveMultiSelect: sandbox.stub(),
setActiveSingleSelectSelection: sandbox.stub(),
};
});
@@ -472,7 +473,7 @@ describe("MultiStageAboutWelcome module", () => {
},
},
navigate: sandbox.stub(),
setActiveSingleSelect: sandbox.stub(),
setActiveSingleSelectSelection: sandbox.stub(),
setActiveMultiSelect: sandbox.stub(),
setScreenMultiSelects: sandbox.stub(),
};
@@ -489,7 +490,9 @@ describe("MultiStageAboutWelcome module", () => {
);
});
it("should preselect the active value if present", async () => {
SINGLE_SELECT_SCREEN_PROPS.activeSingleSelect = "test2";
SINGLE_SELECT_SCREEN_PROPS.activeSingleSelectSelections = {
"tile-0": ["test2"],
};
const wrapper = mount(
<WelcomeScreen {...SINGLE_SELECT_SCREEN_PROPS} />
);
@@ -642,10 +645,14 @@ describe("MultiStageAboutWelcome module", () => {
};
const finishStub = sandbox.stub(global, "AWFinish");
const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
let telemetrySpy = sandbox.spy(
AboutWelcomeUtils,
"sendDismissTelemetry"
);
wrapper.find(".dismiss-button").simulate("click");
assert.calledOnce(finishStub);
assert.calledOnce(telemetrySpy);
});
it("should handle SHOW_FIREFOX_ACCOUNTS", () => {
TEST_ACTION.type = "SHOW_FIREFOX_ACCOUNTS";
@@ -1168,6 +1175,35 @@ describe("MultiStageAboutWelcome module", () => {
assert.notCalled(telemetrySpy);
globals.restore();
});
it("should handle action from matched tile in tiles array", () => {
const TILE_ID = "test-tile-id";
SCREEN_PROPS.content.tiles = [
{
data: [
{
id: TILE_ID,
action: {
type: "OPEN_URL",
data: { args: "https://example.com" },
},
},
],
},
];
const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
wrapper.instance().handleAction({
currentTarget: { value: TILE_ID },
source: "test-source",
name: "test-click",
});
const [calledArg] = AboutWelcomeUtils.handleUserAction.firstCall.args;
const url = new URL(calledArg.data.args);
assert.equal(calledArg.type, "OPEN_URL");
assert.equal(url.hostname, "example.com");
});
});
describe("#handleUserAction", () => {