Bug 1863400 - Part 7: Move about:welcome Karma unit tests to browser/components/aboutwelcome folder. r=pdahiya

Depends on D193121

Differential Revision: https://phabricator.services.mozilla.com/D193122
This commit is contained in:
Mike Conley 2023-11-28 01:37:38 +00:00
parent b443310b40
commit 8f2ec8be56
23 changed files with 1339 additions and 60 deletions

View file

@ -22,7 +22,8 @@ XPCOMUtils.defineLazyModuleGetters(lazy, {
"resource://activity-stream/aboutwelcome/lib/AboutWelcomeTelemetry.jsm",
AboutWelcomeDefaults:
"resource://activity-stream/aboutwelcome/lib/AboutWelcomeDefaults.jsm",
AWScreenUtils: "resource://activity-stream/lib/AWScreenUtils.jsm",
AWScreenUtils:
"resource://activity-stream/aboutwelcome/lib/AWScreenUtils.jsm",
});
XPCOMUtils.defineLazyGetter(lazy, "log", () => {

View file

@ -4,7 +4,7 @@
import React from "react";
import { Localized } from "./MSLocalized";
import { AboutWelcomeUtils } from "../../lib/aboutwelcome-utils";
import { AboutWelcomeUtils } from "../lib/aboutwelcome-utils";
const MS_STRING_PROP = "string_id";
export const HelpText = props => {

View file

@ -0,0 +1,243 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const path = require("path");
const webpack = require("webpack");
const { ResourceUriPlugin } = require("../newtab/tools/resourceUriPlugin");
const PATHS = {
// Where is the entry point for the unit tests?
testEntryFile: path.resolve(__dirname, "./tests/unit/unit-entry.js"),
// A glob-style pattern matching all unit tests
testFilesPattern: "./tests/unit/unit-entry.js",
// The base directory of all source files (used for path resolution in webpack importing)
moduleResolveDirectory: __dirname,
newtabResolveDirectory: "../newtab",
// a RegEx matching all Cu.import statements of local files
resourcePathRegEx: /^resource:\/\/activity-stream\//,
coverageReportingPath: "logs/coverage/",
};
// When tweaking here, be sure to review the docs about the execution ordering
// semantics of the preprocessors array, as they are somewhat odd.
const preprocessors = {};
preprocessors[PATHS.testFilesPattern] = [
"webpack", // require("karma-webpack")
"sourcemap", // require("karma-sourcemap-loader")
];
module.exports = function (config) {
const isTDD = config.tdd;
const browsers = isTDD ? ["Firefox"] : ["FirefoxHeadless"]; // require("karma-firefox-launcher")
config.set({
singleRun: !isTDD,
browsers,
customLaunchers: {
FirefoxHeadless: {
base: "Firefox",
flags: ["--headless"],
},
},
frameworks: [
"chai", // require("chai") require("karma-chai")
"mocha", // require("mocha") require("karma-mocha")
"sinon", // require("sinon") require("karma-sinon")
],
reporters: [
"coverage-istanbul", // require("karma-coverage")
"mocha", // require("karma-mocha-reporter")
// for bin/try-runner.js to parse the output easily
"json", // require("karma-json-reporter")
],
jsonReporter: {
// So this doesn't get interleaved with other karma output
stdout: false,
outputFile: path.join("logs", "karma-run-results.json"),
},
coverageIstanbulReporter: {
reports: ["lcov", "text-summary"], // for some reason "lcov" reallys means "lcov" and "html"
"report-config": {
// so the full m-c path gets printed; needed for https://coverage.moz.tools/ integration
lcov: {
projectRoot: "../../..",
},
},
dir: PATHS.coverageReportingPath,
// This will make karma fail if coverage reporting is less than the minimums here
thresholds: !isTDD && {
each: {
statements: 100,
lines: 100,
functions: 100,
branches: 66,
overrides: {
"modules/*.jsm": {
statements: 0,
lines: 0,
functions: 0,
branches: 0,
},
"content-src/lib/aboutwelcome-utils.js": {
statements: 50,
lines: 50,
functions: 50,
branches: 0,
},
"content-src/components/LanguageSwitcher.jsx": {
// This file is covered by the mochitest: browser_aboutwelcome_multistage_languageSwitcher.js
statements: 0,
lines: 0,
functions: 0,
branches: 0,
},
"content-src/components/EmbeddedMigrationWizard.jsx": {
// This file is covered by the mochitest: browser_aboutwelcome_multistage_mr.js
// Can't be unit tested because it relies on the migration-wizard custom element
statements: 0,
lines: 0,
functions: 0,
branches: 0,
},
"content-src/components/AddonsPicker.jsx": {
// This file is covered by the mochitest: browser_aboutwelcome_multistage_addonspicker.js
statements: 0,
lines: 0,
functions: 0,
branches: 0,
},
"content-src/**/*.jsx": {
statements: 62,
lines: 60,
functions: 50,
branches: 50,
},
"content-src/components/**/*.jsx": {
statements: 51.1,
lines: 52.38,
functions: 31.2,
branches: 31.2,
},
},
},
},
},
files: [PATHS.testEntryFile],
preprocessors,
webpack: {
mode: "none",
devtool: "inline-source-map",
// This loader allows us to override required files in tests
resolveLoader: {
alias: {
inject: path.join(__dirname, "../newtab/loaders/inject-loader"),
},
},
// This resolve config allows us to import with paths relative to the root directory, e.g. "lib/ActivityStream.jsm"
resolve: {
extensions: [".js", ".jsx"],
modules: [
PATHS.moduleResolveDirectory,
"node_modules",
PATHS.newtabResolveDirectory,
],
fallback: {
stream: require.resolve("stream-browserify"),
buffer: require.resolve("buffer"),
},
alias: {
newtab: path.join(__dirname, "../newtab"),
},
},
plugins: [
// The ResourceUriPlugin handles translating resource URIs in import
// statements in .mjs files, in a similar way to what
// babel-jsm-to-commonjs does for jsm files.
new ResourceUriPlugin({
resourcePathRegEx: PATHS.resourcePathRegEx,
}),
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("development"),
}),
],
externals: {
// enzyme needs these for backwards compatibility with 0.13.
// see https://github.com/airbnb/enzyme/blob/master/docs/guides/webpack.md#using-enzyme-with-webpack
"react/addons": true,
"react/lib/ReactContext": true,
"react/lib/ExecutionEnvironment": true,
},
module: {
rules: [
// This rule rewrites importing/exporting in .jsm files to be compatible with esmodules
{
test: /\.jsm$/,
exclude: [/node_modules/],
use: [
{
loader: "babel-loader", // require("babel-core")
options: {
plugins: [
// Converts .jsm files into common-js modules
[
"../newtab/tools/babel-jsm-to-commonjs.js",
{
basePath: PATHS.resourcePathRegEx,
removeOtherImports: true,
replace: true,
},
],
"@babel/plugin-proposal-nullish-coalescing-operator",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-class-properties",
],
},
},
],
},
{
test: /\.js$/,
exclude: [/node_modules\/(?!@fluent\/).*/, /tests/],
loader: "babel-loader",
options: {
// This is a workaround for bug 1787278. It can be removed once
// that bug is fixed.
plugins: ["@babel/plugin-proposal-optional-chaining"],
},
},
{
test: /\.jsx$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: ["@babel/preset-react"],
plugins: [
"@babel/plugin-proposal-nullish-coalescing-operator",
"@babel/plugin-proposal-optional-chaining",
],
},
},
{
test: /\.md$/,
use: "raw-loader",
},
{
enforce: "post",
test: /\.js[mx]?$/,
loader: "@jsdevtools/coverage-istanbul-loader",
options: { esModules: true },
include: [path.resolve("content-src"), path.resolve("modules")],
exclude: [path.resolve("tests"), path.resolve("../newtab")],
},
],
},
},
// Silences some overly-verbose logging of individual module builds
webpackMiddleware: { noInfo: true },
});
};

View file

@ -30,6 +30,7 @@ const AWScreenUtils = {
/**
* Given a JEXL expression, returns the evaluation of the expression or returns
* true if the expression did not evaluate successfully
*
* @param {string} targeting - The JEXL expression that will be evaluated
* @returns {boolean}
*/

View file

@ -21,7 +21,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
});
XPCOMUtils.defineLazyModuleGetters(lazy, {
AWScreenUtils: "resource://activity-stream/lib/AWScreenUtils.jsm",
AWScreenUtils:
"resource://activity-stream/aboutwelcome/lib/AWScreenUtils.jsm",
});
// Message to be updated based on finalized MR designs

View file

@ -13,12 +13,31 @@
"redux": "4.1.2"
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "7.16.0",
"@babel/plugin-proposal-optional-chaining": "7.16.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.16.0",
"@babel/preset-react": "7.16.0",
"@jsdevtools/coverage-istanbul-loader": "^3.0.5",
"babel-loader": "8.2.3",
"babel-plugin-jsm-to-esmodules": "0.6.0",
"chai": "4.3.4",
"chai-json-schema": "1.5.1",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
"karma": "6.3.8",
"karma-chai": "0.1.0",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-firefox-launcher": "2.1.2",
"karma-json-reporter": "1.2.1",
"karma-mocha": "2.0.1",
"karma-mocha-reporter": "2.2.5",
"karma-sinon": "1.0.5",
"karma-sourcemap-loader": "0.3.8",
"karma-webpack": "5.0.0",
"npm-run-all": "4.1.5",
"sass": "1.43.4",
"sinon": "12.0.1",
"stream-browserify": "3.0.0",
"webpack": "5.56.0",
"webpack-cli": "4.9.1",
"yamscripts": "0.1.0"

View file

@ -8,7 +8,7 @@ const { AboutWelcomeTelemetry } = ChromeUtils.import(
"resource://activity-stream/aboutwelcome/lib/AboutWelcomeTelemetry.jsm"
);
const { AWScreenUtils } = ChromeUtils.import(
"resource://activity-stream/lib/AWScreenUtils.jsm"
"resource://activity-stream/aboutwelcome/lib/AWScreenUtils.jsm"
);
const { InternalTestingProfileMigrator } = ChromeUtils.importESModule(
"resource:///modules/InternalTestingProfileMigrator.sys.mjs"

View file

@ -5,7 +5,7 @@ const { getAddonAndLocalAPIsMocker } = ChromeUtils.importESModule(
);
const { AWScreenUtils } = ChromeUtils.import(
"resource://activity-stream/lib/AWScreenUtils.jsm"
"resource://activity-stream/aboutwelcome/lib/AWScreenUtils.jsm"
);
const sandbox = sinon.createSandbox();

View file

@ -8,7 +8,7 @@ const { AboutWelcomeTelemetry } = ChromeUtils.import(
"resource://activity-stream/aboutwelcome/lib/AboutWelcomeTelemetry.jsm"
);
const { AWScreenUtils } = ChromeUtils.import(
"resource://activity-stream/lib/AWScreenUtils.jsm"
"resource://activity-stream/aboutwelcome/lib/AWScreenUtils.jsm"
);
const { InternalTestingProfileMigrator } = ChromeUtils.importESModule(
"resource:///modules/InternalTestingProfileMigrator.sys.mjs"

View file

@ -1,6 +1,6 @@
import { AWScreenUtils } from "lib/AWScreenUtils.jsm";
import { GlobalOverrider } from "test/unit/utils";
import { ASRouter } from "lib/ASRouter.jsm";
import { AWScreenUtils } from "modules/AWScreenUtils.jsm";
import { GlobalOverrider } from "newtab/test/unit/utils";
import { ASRouter } from "newtab/lib/ASRouter.jsm";
describe("AWScreenUtils", () => {
let sandbox;

View file

@ -1,6 +1,6 @@
import React from "react";
import { shallow } from "enzyme";
import { CTAParagraph } from "content-src/aboutwelcome/components/CTAParagraph";
import { CTAParagraph } from "content-src/components/CTAParagraph";
describe("CTAParagraph component", () => {
let sandbox;

View file

@ -1,5 +1,5 @@
import { HelpText } from "content-src/aboutwelcome/components/HelpText";
import { Localized } from "content-src/aboutwelcome/components/MSLocalized";
import { HelpText } from "content-src/components/HelpText";
import { Localized } from "content-src/components/MSLocalized";
import React from "react";
import { shallow } from "enzyme";

View file

@ -1,6 +1,6 @@
import React from "react";
import { shallow } from "enzyme";
import { HeroImage } from "content-src/aboutwelcome/components/HeroImage";
import { HeroImage } from "content-src/components/HeroImage";
describe("HeroImage component", () => {
const imageUrl = "https://example.com";

View file

@ -1,6 +1,6 @@
import React from "react";
import { mount } from "enzyme";
import { LinkParagraph } from "../../../content-src/aboutwelcome/components/LinkParagraph";
import { LinkParagraph } from "content-src/components/LinkParagraph";
describe("LinkParagraph component", () => {
let sandbox;

View file

@ -0,0 +1,328 @@
import React from "react";
import { shallow } from "enzyme";
import {
Colorways,
computeColorWay,
ColorwayDescription,
computeVariationIndex,
} from "content-src/components/MRColorways";
import { WelcomeScreen } from "content-src/components/MultiStageAboutWelcome";
describe("Multistage AboutWelcome module", () => {
let sandbox;
let COLORWAY_SCREEN_PROPS;
beforeEach(() => {
sandbox = sinon.createSandbox();
COLORWAY_SCREEN_PROPS = {
id: "test-colorway-screen",
totalNumberofScreens: 1,
content: {
subtitle: "test subtitle",
tiles: {
type: "colorway",
action: {
theme: "<event>",
},
defaultVariationIndex: 0,
systemVariations: ["automatic", "light"],
variations: ["soft", "bold"],
colorways: [
{
id: "default",
label: "Default",
},
{
id: "abstract",
label: "Abstract",
},
],
},
primary_button: {
action: {},
label: "test button",
},
},
messageId: "test-mr-colorway-screen",
activeTheme: "automatic",
};
});
afterEach(() => {
sandbox.restore();
});
describe("MRColorway component", () => {
it("should render WelcomeScreen", () => {
const wrapper = shallow(<WelcomeScreen {...COLORWAY_SCREEN_PROPS} />);
assert.ok(wrapper.exists());
});
it("should use default when activeTheme is not set", () => {
const wrapper = shallow(<Colorways {...COLORWAY_SCREEN_PROPS} />);
wrapper.setProps({ activeTheme: null });
const colorwaysOptionIcons = wrapper.find(
".tiles-theme-section .theme .icon"
);
assert.strictEqual(colorwaysOptionIcons.length, 2);
// Default automatic theme is selected by default
assert.strictEqual(
colorwaysOptionIcons.first().prop("className").includes("selected"),
true
);
assert.strictEqual(
colorwaysOptionIcons.first().prop("className").includes("default"),
true
);
});
it("should use default when activeTheme is alpenglow", () => {
const wrapper = shallow(<Colorways {...COLORWAY_SCREEN_PROPS} />);
wrapper.setProps({ activeTheme: "alpenglow" });
const colorwaysOptionIcons = wrapper.find(
".tiles-theme-section .theme .icon"
);
assert.strictEqual(colorwaysOptionIcons.length, 2);
// Default automatic theme is selected when unsupported in colorway alpenglow theme is active
assert.strictEqual(
colorwaysOptionIcons.first().prop("className").includes("selected"),
true
);
assert.strictEqual(
colorwaysOptionIcons.first().prop("className").includes("default"),
true
);
});
it("should render colorways options", () => {
const wrapper = shallow(<Colorways {...COLORWAY_SCREEN_PROPS} />);
const colorwaysOptions = wrapper.find(
".tiles-theme-section .theme input[name='theme']"
);
const colorwaysOptionIcons = wrapper.find(
".tiles-theme-section .theme .icon"
);
const colorwaysLabels = wrapper.find(
".tiles-theme-section .theme span.sr-only"
);
assert.strictEqual(colorwaysOptions.length, 2);
assert.strictEqual(colorwaysOptionIcons.length, 2);
assert.strictEqual(colorwaysLabels.length, 2);
// First colorway option
// Default theme radio option is selected by default
assert.strictEqual(
colorwaysOptionIcons.first().prop("className").includes("selected"),
true
);
//Colorway should be using id property
assert.strictEqual(
colorwaysOptions.first().prop("data-colorway"),
"default"
);
// Second colorway option
assert.strictEqual(
colorwaysOptionIcons.last().prop("className").includes("selected"),
false
);
//Colorway should be using id property
assert.strictEqual(
colorwaysOptions.last().prop("data-colorway"),
"abstract"
);
//Colorway should be labelled for screen readers (parent label is for tooltip only, and does not describe the Colorway)
assert.strictEqual(
colorwaysOptions.last().prop("aria-labelledby"),
"abstract-label"
);
});
it("should handle colorway clicks", () => {
sandbox.stub(React, "useEffect").callsFake((fn, vals) => {
if (vals === undefined) {
fn();
} else if (vals[0] === "in") {
fn();
}
});
const handleAction = sandbox.stub();
const wrapper = shallow(
<Colorways handleAction={handleAction} {...COLORWAY_SCREEN_PROPS} />
);
const colorwaysOptions = wrapper.find(
".tiles-theme-section .theme input[name='theme']"
);
let props = wrapper.find(ColorwayDescription).props();
assert.propertyVal(props.colorway, "label", "Default");
const option = colorwaysOptions.last();
assert.propertyVal(option.props(), "value", "abstract-soft");
colorwaysOptions.last().simulate("click");
assert.calledOnce(handleAction);
});
it("should render colorway description", () => {
const wrapper = shallow(<Colorways {...COLORWAY_SCREEN_PROPS} />);
let descriptionsWrapper = wrapper.find(ColorwayDescription);
assert.ok(descriptionsWrapper.exists());
let props = descriptionsWrapper.props();
// Colorway description should display Default theme desc by default
assert.strictEqual(props.colorway.label, "Default");
});
it("ColorwayDescription should display active colorway desc", () => {
let TEST_COLORWAY_PROPS = {
colorway: {
label: "Activist",
description: "Test Activist",
},
};
const descWrapper = shallow(
<ColorwayDescription {...TEST_COLORWAY_PROPS} />
);
assert.ok(descWrapper.exists());
const descText = descWrapper.find(".colorway-text");
assert.equal(
descText.props()["data-l10n-args"].includes("Activist"),
true
);
});
it("should computeColorWayId for default active theme", () => {
let TEST_COLORWAY_PROPS = {
...COLORWAY_SCREEN_PROPS,
};
const colorwayId = computeColorWay(
TEST_COLORWAY_PROPS.activeTheme,
TEST_COLORWAY_PROPS.content.tiles.systemVariations
);
assert.strictEqual(colorwayId, "default");
});
it("should computeColorWayId for non-default active theme", () => {
let TEST_COLORWAY_PROPS = {
...COLORWAY_SCREEN_PROPS,
activeTheme: "abstract-soft",
};
const colorwayId = computeColorWay(
TEST_COLORWAY_PROPS.activeTheme,
TEST_COLORWAY_PROPS.content.tiles.systemVariations
);
assert.strictEqual(colorwayId, "abstract");
});
it("should computeVariationIndex for default active theme", () => {
let TEST_COLORWAY_PROPS = {
...COLORWAY_SCREEN_PROPS,
};
const variationIndex = computeVariationIndex(
TEST_COLORWAY_PROPS.activeTheme,
TEST_COLORWAY_PROPS.content.tiles.systemVariations,
TEST_COLORWAY_PROPS.content.tiles.variations,
TEST_COLORWAY_PROPS.content.tiles.defaultVariationIndex
);
assert.strictEqual(
variationIndex,
TEST_COLORWAY_PROPS.content.tiles.defaultVariationIndex
);
});
it("should computeVariationIndex for active theme", () => {
let TEST_COLORWAY_PROPS = {
...COLORWAY_SCREEN_PROPS,
};
const variationIndex = computeVariationIndex(
"light",
TEST_COLORWAY_PROPS.content.tiles.systemVariations,
TEST_COLORWAY_PROPS.content.tiles.variations,
TEST_COLORWAY_PROPS.content.tiles.defaultVariationIndex
);
assert.strictEqual(variationIndex, 1);
});
it("should computeVariationIndex for colorway theme", () => {
let TEST_COLORWAY_PROPS = {
...COLORWAY_SCREEN_PROPS,
};
const variationIndex = computeVariationIndex(
"abstract-bold",
TEST_COLORWAY_PROPS.content.tiles.systemVariations,
TEST_COLORWAY_PROPS.content.tiles.variations,
TEST_COLORWAY_PROPS.content.tiles.defaultVariationIndex
);
assert.strictEqual(variationIndex, 1);
});
describe("random colorways", () => {
let test;
beforeEach(() => {
COLORWAY_SCREEN_PROPS.handleAction = sandbox.stub();
sandbox.stub(window, "matchMedia");
// eslint-disable-next-line max-nested-callbacks
sandbox.stub(React, "useEffect").callsFake((fn, vals) => {
if (vals?.length === 0) {
fn();
}
});
test = () => {
shallow(<Colorways {...COLORWAY_SCREEN_PROPS} />);
return COLORWAY_SCREEN_PROPS.handleAction.firstCall.firstArg
.currentTarget;
};
});
it("should select a random colorway", () => {
const { value } = test();
assert.strictEqual(value, "abstract-soft");
assert.calledThrice(React.useEffect);
assert.notCalled(window.matchMedia);
});
it("should select a random soft colorway when not dark", () => {
window.matchMedia.returns({ matches: false });
COLORWAY_SCREEN_PROPS.content.tiles.darkVariation = 1;
const { value } = test();
assert.strictEqual(value, "abstract-soft");
assert.calledThrice(React.useEffect);
assert.calledOnce(window.matchMedia);
});
it("should select a random bold colorway when dark", () => {
window.matchMedia.returns({ matches: true });
COLORWAY_SCREEN_PROPS.content.tiles.darkVariation = 1;
const { value } = test();
assert.strictEqual(value, "abstract-bold");
assert.calledThrice(React.useEffect);
assert.calledOnce(window.matchMedia);
});
});
});
});

View file

@ -1,4 +1,4 @@
import { Localized } from "content-src/aboutwelcome/components/MSLocalized";
import { Localized } from "content-src/components/MSLocalized";
import React from "react";
import { shallow } from "enzyme";

View file

@ -1,7 +1,7 @@
import React from "react";
import { shallow, mount } from "enzyme";
import { GlobalOverrider } from "test/unit/utils";
import { MobileDownloads } from "content-src/aboutwelcome/components/MobileDownloads";
import { GlobalOverrider } from "newtab/test/unit/utils";
import { MobileDownloads } from "content-src/components/MobileDownloads";
describe("Multistage AboutWelcome MobileDownloads module", () => {
let globals;

View file

@ -1,6 +1,6 @@
import React from "react";
import { mount } from "enzyme";
import { MultiSelect } from "content-src/aboutwelcome/components/MultiSelect";
import { MultiSelect } from "content-src/components/MultiSelect";
describe("Multistage AboutWelcome module", () => {
let sandbox;

View file

@ -1,6 +1,6 @@
import { AboutWelcomeDefaults } from "aboutwelcome/lib/AboutWelcomeDefaults.jsm";
import { MultiStageProtonScreen } from "content-src/aboutwelcome/components/MultiStageProtonScreen";
import { AWScreenUtils } from "lib/AWScreenUtils.jsm";
import { AboutWelcomeDefaults } from "modules/AboutWelcomeDefaults.jsm";
import { MultiStageProtonScreen } from "content-src/components/MultiStageProtonScreen";
import { AWScreenUtils } from "modules/AWScreenUtils.jsm";
import React from "react";
import { mount } from "enzyme";

View file

@ -1,15 +1,15 @@
import { GlobalOverrider } from "test/unit/utils";
import { GlobalOverrider } from "newtab/test/unit/utils";
import {
MultiStageAboutWelcome,
SecondaryCTA,
StepsIndicator,
ProgressBar,
WelcomeScreen,
} from "content-src/aboutwelcome/components/MultiStageAboutWelcome";
import { Themes } from "content-src/aboutwelcome/components/Themes";
} from "content-src/components/MultiStageAboutWelcome";
import { Themes } from "content-src/components/Themes";
import React from "react";
import { shallow, mount } from "enzyme";
import { AboutWelcomeDefaults } from "aboutwelcome/lib/AboutWelcomeDefaults.jsm";
import { AboutWelcomeDefaults } from "modules/AboutWelcomeDefaults.jsm";
import { AboutWelcomeUtils } from "content-src/lib/aboutwelcome-utils";
describe("MultiStageAboutWelcome module", () => {

View file

@ -1,6 +1,6 @@
import React from "react";
import { mount } from "enzyme";
import { OnboardingVideo } from "content-src/aboutwelcome/components/OnboardingVideo";
import { OnboardingVideo } from "content-src/components/OnboardingVideo";
describe("OnboardingVideo component", () => {
let sandbox;

View file

@ -0,0 +1,720 @@
import {
EventEmitter,
FakePrefs,
FakensIPrefService,
GlobalOverrider,
FakeConsoleAPI,
FakeLogger,
} from "newtab/test/unit/utils";
import Adapter from "enzyme-adapter-react-16";
import { chaiAssertions } from "newtab/test/schemas/pings";
import chaiJsonSchema from "chai-json-schema";
import enzyme from "enzyme";
import FxMSCommonSchema from "newtab/content-src/asrouter/schemas/FxMSCommon.schema.json";
enzyme.configure({ adapter: new Adapter() });
// Cause React warnings to make tests that trigger them fail
const origConsoleError = console.error;
console.error = function (msg, ...args) {
origConsoleError.apply(console, [msg, ...args]);
if (
/(Invalid prop|Failed prop type|Check the render method|React Intl)/.test(
msg
)
) {
throw new Error(msg);
}
};
const req = require.context(".", true, /\.test\.jsx?$/);
const files = req.keys();
// This exposes sinon assertions to chai.assert
sinon.assert.expose(assert, { prefix: "" });
chai.use(chaiAssertions);
chai.use(chaiJsonSchema);
chai.tv4.addSchema("file:///FxMSCommon.schema.json", FxMSCommonSchema);
const overrider = new GlobalOverrider();
const RemoteSettings = name => ({
get: () => {
if (name === "attachment") {
return Promise.resolve([{ attachment: {} }]);
}
return Promise.resolve([]);
},
on: () => {},
off: () => {},
});
RemoteSettings.pollChanges = () => {};
class JSWindowActorParent {
sendAsyncMessage(name, data) {
return { name, data };
}
}
class JSWindowActorChild {
sendAsyncMessage(name, data) {
return { name, data };
}
sendQuery(name, data) {
return Promise.resolve({ name, data });
}
get contentWindow() {
return {
Promise,
};
}
}
// Detect plain object passed to lazy getter APIs, and set its prototype to
// global object, and return the global object for further modification.
// Returns the object if it's not plain object.
//
// This is a workaround to make the existing testharness and testcase keep
// working even after lazy getters are moved to plain `lazy` object.
const cachedPlainObject = new Set();
function updateGlobalOrObject(object) {
// Given this function modifies the prototype, and the following
// condition doesn't meet on the second call, cache the result.
if (cachedPlainObject.has(object)) {
return global;
}
if (Object.getPrototypeOf(object).constructor.name !== "Object") {
return object;
}
cachedPlainObject.add(object);
Object.setPrototypeOf(object, global);
return global;
}
const TEST_GLOBAL = {
JSWindowActorParent,
JSWindowActorChild,
AboutReaderParent: {
addMessageListener: (messageName, listener) => {},
removeMessageListener: (messageName, listener) => {},
},
AboutWelcomeTelemetry: class {
submitGleanPingForPing() {}
},
AddonManager: {
getActiveAddons() {
return Promise.resolve({ addons: [], fullData: false });
},
},
AppConstants: {
MOZILLA_OFFICIAL: true,
MOZ_APP_VERSION: "69.0a1",
isChinaRepack() {
return false;
},
isPlatformAndVersionAtMost() {
return false;
},
platform: "win",
},
ASRouterPreferences: {
console: new FakeConsoleAPI({
maxLogLevel: "off", // set this to "debug" or "all" to get more ASRouter logging in tests
prefix: "ASRouter",
}),
},
AWScreenUtils: {
evaluateTargetingAndRemoveScreens() {
return true;
},
async removeScreens() {
return true;
},
evaluateScreenTargeting() {
return true;
},
},
BrowserUtils: {
sendToDeviceEmailsSupported() {
return true;
},
},
UpdateUtils: { getUpdateChannel() {} },
BasePromiseWorker: class {
constructor() {
this.ExceptionHandlers = [];
}
post() {}
},
browserSearchRegion: "US",
BrowserWindowTracker: { getTopWindow() {} },
ChromeUtils: {
defineModuleGetter: updateGlobalOrObject,
defineESModuleGetters: updateGlobalOrObject,
generateQI() {
return {};
},
import() {
return global;
},
importESModule() {
return global;
},
},
ClientEnvironment: {
get userId() {
return "foo123";
},
},
Components: {
Constructor(classId) {
switch (classId) {
case "@mozilla.org/referrer-info;1":
return function (referrerPolicy, sendReferrer, originalReferrer) {
this.referrerPolicy = referrerPolicy;
this.sendReferrer = sendReferrer;
this.originalReferrer = originalReferrer;
};
}
return function () {};
},
isSuccessCode: () => true,
},
ConsoleAPI: FakeConsoleAPI,
// NB: These are functions/constructors
// eslint-disable-next-line object-shorthand
ContentSearchUIController: function () {},
// eslint-disable-next-line object-shorthand
ContentSearchHandoffUIController: function () {},
Cc: {
"@mozilla.org/browser/nav-bookmarks-service;1": {
addObserver() {},
getService() {
return this;
},
removeObserver() {},
SOURCES: {},
TYPE_BOOKMARK: {},
},
"@mozilla.org/browser/nav-history-service;1": {
addObserver() {},
executeQuery() {},
getNewQuery() {},
getNewQueryOptions() {},
getService() {
return this;
},
insert() {},
markPageAsTyped() {},
removeObserver() {},
},
"@mozilla.org/io/string-input-stream;1": {
createInstance() {
return {};
},
},
"@mozilla.org/security/hash;1": {
createInstance() {
return {
init() {},
updateFromStream() {},
finish() {
return "0";
},
};
},
},
"@mozilla.org/updates/update-checker;1": { createInstance() {} },
"@mozilla.org/widget/useridleservice;1": {
getService() {
return {
idleTime: 0,
addIdleObserver() {},
removeIdleObserver() {},
};
},
},
"@mozilla.org/streamConverters;1": {
getService() {
return this;
},
},
"@mozilla.org/network/stream-loader;1": {
createInstance() {
return {};
},
},
},
Ci: {
nsICryptoHash: {},
nsIReferrerInfo: { UNSAFE_URL: 5 },
nsITimer: { TYPE_ONE_SHOT: 1 },
nsIWebProgressListener: { LOCATION_CHANGE_SAME_DOCUMENT: 1 },
nsIDOMWindow: Object,
nsITrackingDBService: {
TRACKERS_ID: 1,
TRACKING_COOKIES_ID: 2,
CRYPTOMINERS_ID: 3,
FINGERPRINTERS_ID: 4,
SOCIAL_ID: 5,
},
nsICookieBannerService: {
MODE_DISABLED: 0,
MODE_REJECT: 1,
MODE_REJECT_OR_ACCEPT: 2,
MODE_UNSET: 3,
},
},
Cu: {
importGlobalProperties() {},
now: () => window.performance.now(),
cloneInto: o => JSON.parse(JSON.stringify(o)),
},
console: {
...console,
error() {},
},
dump() {},
EveryWindow: {
registerCallback: (id, init, uninit) => {},
unregisterCallback: id => {},
},
setTimeout: window.setTimeout.bind(window),
clearTimeout: window.clearTimeout.bind(window),
fetch() {},
// eslint-disable-next-line object-shorthand
Image: function () {}, // NB: This is a function/constructor
IOUtils: {
writeJSON() {
return Promise.resolve(0);
},
readJSON() {
return Promise.resolve({});
},
read() {
return Promise.resolve(new Uint8Array());
},
makeDirectory() {
return Promise.resolve(0);
},
write() {
return Promise.resolve(0);
},
exists() {
return Promise.resolve(0);
},
remove() {
return Promise.resolve(0);
},
stat() {
return Promise.resolve(0);
},
},
NewTabUtils: {
activityStreamProvider: {
getTopFrecentSites: () => [],
executePlacesQuery: async (sql, options) => ({ sql, options }),
},
},
OS: {
File: {
writeAtomic() {},
makeDir() {},
stat() {},
Error: {},
read() {},
exists() {},
remove() {},
removeEmptyDir() {},
},
Path: {
join() {
return "/";
},
},
Constants: {
Path: {
localProfileDir: "/",
},
},
},
PathUtils: {
join(...parts) {
return parts[parts.length - 1];
},
joinRelative(...parts) {
return parts[parts.length - 1];
},
getProfileDir() {
return Promise.resolve("/");
},
getLocalProfileDir() {
return Promise.resolve("/");
},
},
PlacesUtils: {
get bookmarks() {
return TEST_GLOBAL.Cc["@mozilla.org/browser/nav-bookmarks-service;1"];
},
get history() {
return TEST_GLOBAL.Cc["@mozilla.org/browser/nav-history-service;1"];
},
observers: {
addListener() {},
removeListener() {},
},
},
Preferences: FakePrefs,
PrivateBrowsingUtils: {
isBrowserPrivate: () => false,
isWindowPrivate: () => false,
permanentPrivateBrowsing: false,
},
DownloadsViewUI: {
getDisplayName: () => "filename.ext",
getSizeWithUnits: () => "1.5 MB",
},
FileUtils: {
// eslint-disable-next-line object-shorthand
File: function () {}, // NB: This is a function/constructor
},
Region: {
home: "US",
REGION_TOPIC: "browser-region-updated",
},
Services: {
dirsvc: {
get: () => ({ parent: { parent: { path: "appPath" } } }),
},
env: {
set: () => undefined,
},
locale: {
get appLocaleAsBCP47() {
return "en-US";
},
negotiateLanguages() {},
},
urlFormatter: { formatURL: str => str, formatURLPref: str => str },
mm: {
addMessageListener: (msg, cb) => this.receiveMessage(),
removeMessageListener() {},
},
obs: {
addObserver() {},
removeObserver() {},
notifyObservers() {},
},
telemetry: {
setEventRecordingEnabled: () => {},
recordEvent: eventDetails => {},
scalarSet: () => {},
keyedScalarAdd: () => {},
},
uuid: {
generateUUID() {
return "{foo-123-foo}";
},
},
console: { logStringMessage: () => {} },
prefs: new FakensIPrefService(),
tm: {
dispatchToMainThread: cb => cb(),
idleDispatchToMainThread: cb => cb(),
},
eTLD: {
getBaseDomain({ spec }) {
return spec.match(/\/([^/]+)/)[1];
},
getBaseDomainFromHost(host) {
return host.match(/.*?(\w+\.\w+)$/)[1];
},
getPublicSuffix() {},
},
io: {
newURI: spec => ({
mutate: () => ({
setRef: ref => ({
finalize: () => ({
ref,
spec,
}),
}),
}),
spec,
}),
},
search: {
init() {
return Promise.resolve();
},
getVisibleEngines: () =>
Promise.resolve([{ identifier: "google" }, { identifier: "bing" }]),
defaultEngine: {
identifier: "google",
searchForm:
"https://www.google.com/search?q=&ie=utf-8&oe=utf-8&client=firefox-b",
aliases: ["@google"],
},
defaultPrivateEngine: {
identifier: "bing",
searchForm: "https://www.bing.com",
aliases: ["@bing"],
},
getEngineByAlias: async () => null,
},
scriptSecurityManager: {
createNullPrincipal() {},
getSystemPrincipal() {},
},
wm: {
getMostRecentWindow: () => window,
getMostRecentBrowserWindow: () => window,
getEnumerator: () => [],
},
ww: { registerNotification() {}, unregisterNotification() {} },
appinfo: { appBuildID: "20180710100040", version: "69.0a1" },
scriptloader: { loadSubScript: () => {} },
startup: {
getStartupInfo() {
return {
process: {
getTime() {
return 1588010448000;
},
},
};
},
},
},
XPCOMUtils: {
defineLazyGetter(object, name, f) {
updateGlobalOrObject(object)[name] = f();
},
defineLazyGlobalGetters: updateGlobalOrObject,
defineLazyModuleGetters: updateGlobalOrObject,
defineLazyServiceGetter: updateGlobalOrObject,
defineLazyServiceGetters: updateGlobalOrObject,
defineLazyPreferenceGetter(object, name) {
updateGlobalOrObject(object)[name] = "";
},
generateQI() {
return {};
},
},
EventEmitter,
ShellService: {
doesAppNeedPin: () => false,
isDefaultBrowser: () => true,
},
FilterExpressions: {
eval() {
return Promise.resolve(false);
},
},
RemoteSettings,
Localization: class {
async formatMessages(stringsIds) {
return Promise.resolve(
stringsIds.map(({ id, args }) => ({ value: { string_id: id, args } }))
);
}
async formatValue(stringId) {
return Promise.resolve(stringId);
}
},
FxAccountsConfig: {
promiseConnectAccountURI(id) {
return Promise.resolve(id);
},
},
FX_MONITOR_OAUTH_CLIENT_ID: "fake_client_id",
ExperimentAPI: {
getExperiment() {},
getExperimentMetaData() {},
getRolloutMetaData() {},
},
NimbusFeatures: {
glean: {
getVariable() {},
},
newtab: {
getVariable() {},
getAllVariables() {},
onUpdate() {},
offUpdate() {},
},
pocketNewtab: {
getVariable() {},
getAllVariables() {},
onUpdate() {},
offUpdate() {},
},
cookieBannerHandling: {
getVariable() {},
},
},
TelemetryEnvironment: {
setExperimentActive() {},
currentEnvironment: {
profile: {
creationDate: 16587,
},
settings: {},
},
},
TelemetryStopwatch: {
start: () => {},
finish: () => {},
},
Sampling: {
ratioSample(seed, ratios) {
return Promise.resolve(0);
},
},
BrowserHandler: {
get kiosk() {
return false;
},
},
TelemetrySession: {
getMetadata(reason) {
return {
reason,
sessionId: "fake_session_id",
};
},
},
PageThumbs: {
addExpirationFilter() {},
removeExpirationFilter() {},
},
Logger: FakeLogger,
getFxAccountsSingleton() {},
AboutNewTab: {},
Glean: {
newtab: {
opened: {
record() {},
},
closed: {
record() {},
},
locale: {
set() {},
},
newtabCategory: {
set() {},
},
homepageCategory: {
set() {},
},
blockedSponsors: {
set() {},
},
sovAllocation: {
set() {},
},
},
newtabSearch: {
enabled: {
set() {},
},
},
pocket: {
enabled: {
set() {},
},
impression: {
record() {},
},
isSignedIn: {
set() {},
},
sponsoredStoriesEnabled: {
set() {},
},
click: {
record() {},
},
save: {
record() {},
},
topicClick: {
record() {},
},
},
topsites: {
enabled: {
set() {},
},
sponsoredEnabled: {
set() {},
},
impression: {
record() {},
},
click: {
record() {},
},
rows: {
set() {},
},
showPrivacyClick: {
record() {},
},
dismiss: {
record() {},
},
prefChanged: {
record() {},
},
},
topSites: {
pingType: {
set() {},
},
position: {
set() {},
},
source: {
set() {},
},
tileId: {
set() {},
},
reportingUrl: {
set() {},
},
advertiser: {
set() {},
},
contextId: {
set() {},
},
},
},
GleanPings: {
newtab: {
submit() {},
},
topSites: {
submit() {},
},
},
Utils: {
SERVER_URL: "bogus://foo",
},
};
overrider.set(TEST_GLOBAL);
describe("activity-stream", () => {
after(() => overrider.restore());
files.forEach(file => req(file));
});

View file

@ -206,46 +206,12 @@ module.exports = function (config) {
functions: 0,
branches: 0,
},
"content-src/lib/aboutwelcome-utils.js": {
statements: 50,
lines: 50,
functions: 50,
branches: 0,
},
"content-src/lib/link-menu-options.js": {
statements: 96,
lines: 96,
functions: 96,
branches: 70,
},
"content-src/aboutwelcome/components/LanguageSwitcher.jsx": {
// This file is covered by the mochitest: browser_aboutwelcome_multistage_languageSwitcher.js
statements: 0,
lines: 0,
functions: 0,
branches: 0,
},
"content-src/aboutwelcome/components/EmbeddedMigrationWizard.jsx": {
// This file is covered by the mochitest: browser_aboutwelcome_multistage_mr.js
// Can't be unit tested because it relies on the migration-wizard custom element
statements: 0,
lines: 0,
functions: 0,
branches: 0,
},
"content-src/aboutwelcome/components/AddonsPicker.jsx": {
// This file is covered by the mochitest: browser_aboutwelcome_multistage_addonspicker.js
statements: 0,
lines: 0,
functions: 0,
branches: 0,
},
"content-src/aboutwelcome/**/*.jsx": {
statements: 62,
lines: 60,
functions: 50,
branches: 50,
},
"content-src/components/**/*.jsx": {
statements: 51.1,
lines: 52.38,