From a320faac4ce626668acaf9003869e7e8d12baed1 Mon Sep 17 00:00:00 2001 From: tiftran Date: Thu, 11 Jun 2020 00:25:28 -0700 Subject: [PATCH 1/2] enforce isEnrollmentPaused for required recipes --- .../recipes/form/ActionArguments.js | 1 + .../recipes/form/RecipeFormHeader.js | 27 +- tests/RecipeForm.test.js | 254 ++++++++++++++++-- tests/factories/recipeFactory.js | 53 ++++ 4 files changed, 307 insertions(+), 28 deletions(-) diff --git a/extension/content/components/recipes/form/ActionArguments.js b/extension/content/components/recipes/form/ActionArguments.js index 46306188..9b49617e 100644 --- a/extension/content/components/recipes/form/ActionArguments.js +++ b/extension/content/components/recipes/form/ActionArguments.js @@ -41,6 +41,7 @@ export const INITIAL_ACTION_ARGUMENTS = { "multi-preference-experiment": { branches: [], experimentDocumentUrl: "", + isEnrollmentPaused: false, slug: "", userFacingDescription: "", userFacingName: "", diff --git a/extension/content/components/recipes/form/RecipeFormHeader.js b/extension/content/components/recipes/form/RecipeFormHeader.js index eeaa35a9..24918bef 100644 --- a/extension/content/components/recipes/form/RecipeFormHeader.js +++ b/extension/content/components/recipes/form/RecipeFormHeader.js @@ -3,6 +3,7 @@ import React from "react"; import { useHistory, useParams } from "react-router-dom"; import { Alert, Button, Icon, IconButton, Input, Modal } from "rsuite"; +import { INITIAL_ACTION_ARGUMENTS } from "devtools/components/recipes/form/ActionArguments"; import { useEnvironmentState, useSelectedNormandyEnvironmentAPI, @@ -33,18 +34,34 @@ export default function RecipeFormHeader() { } }; + const isEnrollmentPausedRequired = (action, data) => { + const { name } = action; + const { arguments: args } = data; + const intitation_action_args = INITIAL_ACTION_ARGUMENTS[name]; + return ( + !("isEnrollmentPaused" in args) && + "isEnrollmentPaused" in intitation_action_args + ); + }; + const saveRecipe = (comment) => { const { action, ...cleanedData } = data; + let id; if (!history.location.pathname.includes("clone")) { id = recipeId; } - const requestSave = normandyApi.saveRecipe(id, { - ...cleanedData, - comment, - action_id: action.id, - }); + let saveData = { ...cleanedData, comment, action_id: action.id }; + + if (isEnrollmentPausedRequired(action, cleanedData)) { + saveData = { + ...saveData, + arguments: { ...saveData.arguments, isEnrollmentPaused: false }, + }; + } + + const requestSave = normandyApi.saveRecipe(id, saveData); requestSave .then((savedRecipe) => { diff --git a/tests/RecipeForm.test.js b/tests/RecipeForm.test.js index c140d32a..ab4edab8 100644 --- a/tests/RecipeForm.test.js +++ b/tests/RecipeForm.test.js @@ -17,7 +17,11 @@ import { ChannelFilterObjectFactory, BucketSampleFilterObjectFactory, } from "./factories/filterObjectFactory"; -import { RecipeFactory } from "./factories/recipeFactory"; +import { + RecipeFactory, + MultiPrefBranchFactory, + MultiPreferenceFactory, +} from "./factories/recipeFactory"; describe("The `RecipeForm` component", () => { afterEach(() => { @@ -91,24 +95,7 @@ describe("The `RecipeForm` component", () => { }; }; - const setup = () => { - const versions = VersionFilterObjectFactory.build( - {}, - { generateVersionsCount: 2 }, - ); - const channels = ChannelFilterObjectFactory.build( - {}, - { generateChannelsCount: 1 }, - ); - const sample = BucketSampleFilterObjectFactory.build(); - const filterObject = [versions, sample, channels]; - const recipe = RecipeFactory.build( - {}, - { - actionName: "console-log", - filterObject, - }, - ); + const setup = (recipe) => { const pageResponse = { results: [recipe] }; const filtersResponse = FiltersFactory.build( {}, @@ -132,12 +119,58 @@ describe("The `RecipeForm` component", () => { jest .spyOn(NormandyAPI.prototype, "fetchRecipe") .mockImplementation(() => Promise.resolve(recipe)); + }; + + const consoleLogRecipeSetup = () => { + const versions = VersionFilterObjectFactory.build( + {}, + { generateVersionsCount: 2 }, + ); + const channels = ChannelFilterObjectFactory.build( + {}, + { generateChannelsCount: 1 }, + ); + const sample = BucketSampleFilterObjectFactory.build(); + const filterObject = [versions, sample, channels]; + return RecipeFactory.build( + {}, + { + actionName: "console-log", + filterObject, + }, + ); + }; + + const multiprefRecipeSetUp = () => { + const versions = VersionFilterObjectFactory.build( + {}, + { generateVersionsCount: 2 }, + ); + const recipe = RecipeFactory.build( + {}, + { + actionName: "multi-preference-experiment", + filterObject: [versions], + }, + ); + const branch1 = MultiPrefBranchFactory.build( + {}, + { generatePreferenceCount: 2 }, + ); + const branch2 = MultiPrefBranchFactory.build( + {}, + { generatePreferenceCount: 1 }, + ); + const branches = [branch1, branch2]; + const multiPrefArguments = MultiPreferenceFactory.build({ branches }); + recipe.latest_revision.arguments = multiPrefArguments; return recipe; }; it("creation recipe form", async () => { - setup(); + const recipe = RecipeFactory.build(); + setup(recipe); const { getByText, getAllByRole } = await render(); fireEvent.click(getByText("Create Recipe")); await waitFor(() => @@ -277,7 +310,8 @@ describe("The `RecipeForm` component", () => { }); it("edit recipe form", async () => { - const recipeData = setup(); + const recipeData = consoleLogRecipeSetup(); + setup(recipeData); const { getByText, getAllByRole } = await render(); await waitFor(() => { @@ -362,11 +396,11 @@ describe("The `RecipeForm` component", () => { }); it("save button is re-enabled when form errors are addressed", async () => { - const recipeData = setup(); + const recipeData = consoleLogRecipeSetup(); + setup(recipeData); const { getByText, getAllByRole } = await render(); fireEvent.click(getByText("Recipes")); - await waitFor(() => { expect(getByText("Edit Recipe")).toBeInTheDocument(); }); @@ -440,4 +474,178 @@ describe("The `RecipeForm` component", () => { updatedRecipeData, ); }); + + it("should add missing isEnrollmentPaused to edited multipref experiment recipe ", async () => { + const recipeData = multiprefRecipeSetUp(); + setup(recipeData); + const { getByText, getAllByRole } = await render(); + + fireEvent.click(getByText("Recipes")); + + await waitFor(() => { + expect(getByText("Edit Recipe")).toBeInTheDocument(); + }); + fireEvent.click(getByText("Edit Recipe")); + await waitFor(() => { + expect(getByText("Experimenter Slug")).toBeInTheDocument(); + }); + + let formGroups = getAllByRole("group"); + + const userFacingForm = findForm(formGroups, "User Facing Name"); + const highVolumeForm = findForm(formGroups, "High Volume Recipe"); + const BranchForm = findForm(formGroups, "Branches"); + + const userFacingInput = userFacingForm.querySelector("input"); + const highVolumeToggle = within(highVolumeForm).getByRole("button"); + const firstDeleteBranchButton = BranchForm.querySelectorAll("button")[0]; + + const userFacingName = "A new user facing name"; + + fireEvent.change(userFacingInput, { target: { value: userFacingName } }); + fireEvent.click(highVolumeToggle); + fireEvent.click(firstDeleteBranchButton); + + formGroups = getAllByRole("group"); + const preferences = findForm(formGroups, "Preferences"); + + const prefButtons = preferences.querySelectorAll("button"); + const prefDeleteButton = prefButtons[0]; + fireEvent.click(prefDeleteButton); + + fireEvent.click(getByText("Add Preference")); + + formGroups = getAllByRole("group"); + const prefForm = findForm(formGroups, "Preferences"); + + // find preference fields + let prefFieldForms = within(prefForm).getAllByRole("group"); + const prefNameForm = findForm(prefFieldForms, "Name"); + const prefBranchTypeForm = findForm( + prefFieldForms, + "Preference Branch Type", + ); + const prefTypeForm = findForm(prefFieldForms, "Preference Type"); + const prefNameInput = prefNameForm.querySelector("input"); + + const prefNameValue = "pref1"; + const prefBranchTypeValue = "Default"; + const prefTypeValue = "Integer"; + const prefValueValue = 22; + + fireEvent.change(prefNameInput, { target: { value: prefNameValue } }); + fireEvent.click(within(prefBranchTypeForm).getByRole("combobox")); + fireEvent.click(getByText(prefBranchTypeValue)); + fireEvent.click(within(prefTypeForm).getByRole("combobox")); + fireEvent.click(getByText(prefTypeValue)); + + prefFieldForms = within(prefForm).getAllByRole("group"); + const prefValueForm = findForm(prefFieldForms, "Value"); + const prefValueInput = prefValueForm.querySelector("input"); + + fireEvent.change(prefValueInput, { target: { value: prefValueValue } }); + + fireEvent.click(getByText("Save")); + + const modalDialog = getAllByRole("dialog")[0]; + const commentInput = modalDialog.querySelector("textArea"); + const saveMessage = "Edited Recipe"; + fireEvent.change(commentInput, { target: { value: saveMessage } }); + + fireEvent.click(within(modalDialog).getByText("Save")); + + const { latest_revision } = recipeData; + /* eslint-disable prefer-const */ + let { + action, + comment: _omitComment, + ...updatedRecipeData + } = latest_revision; + /* eslint-enable prefer-const */ + const { branches } = updatedRecipeData.arguments; + const branch = branches[1]; + const updatedBranches = [ + { + ...branch, + preferences: { + [prefNameValue]: { + preferenceBranchType: "default", + preferenceType: "integer", + preferenceValue: prefValueValue, + }, + }, + }, + ]; + updatedRecipeData = { + ...updatedRecipeData, + comment: saveMessage, + action_id: action.id, + arguments: { + ...updatedRecipeData.arguments, + userFacingName, + isHighPopulation: true, + isEnrollmentPaused: false, + branches: updatedBranches, + }, + }; + expect(NormandyAPI.prototype.saveRecipe).toBeCalledWith( + recipeData.id.toString(), + updatedRecipeData, + ); + }); + + it("should leave isEnrollmentPaused when it's already set ", async () => { + const recipeData = multiprefRecipeSetUp(); + recipeData.latest_revision.arguments.isEnrollmentPaused = true; + setup(recipeData); + const { getByText, getAllByRole } = await render(); + + fireEvent.click(getByText("Recipes")); + + await waitFor(() => { + expect(getByText("Edit Recipe")).toBeInTheDocument(); + }); + fireEvent.click(getByText("Edit Recipe")); + await waitFor(() => { + expect(getByText("Experimenter Slug")).toBeInTheDocument(); + }); + + const formGroups = getAllByRole("group"); + const highVolumeForm = findForm(formGroups, "High Volume Recipe"); + const highVolumeToggle = within(highVolumeForm).getByRole("button"); + + fireEvent.click(highVolumeToggle); + + fireEvent.click(getByText("Save")); + + const modalDialog = getAllByRole("dialog")[0]; + const commentInput = modalDialog.querySelector("textArea"); + const saveMessage = "Edited Recipe"; + fireEvent.change(commentInput, { target: { value: saveMessage } }); + + fireEvent.click(within(modalDialog).getByText("Save")); + + const { latest_revision } = recipeData; + /* eslint-disable prefer-const */ + let { + action, + comment: _omitComment, + ...updatedRecipeData + } = latest_revision; + /* eslint-enable prefer-const */ + + updatedRecipeData = { + ...updatedRecipeData, + comment: saveMessage, + action_id: action.id, + arguments: { + ...updatedRecipeData.arguments, + isHighPopulation: true, + }, + }; + expect(NormandyAPI.prototype.saveRecipe).toBeCalledWith( + recipeData.id.toString(), + updatedRecipeData, + ); + }); }); diff --git a/tests/factories/recipeFactory.js b/tests/factories/recipeFactory.js index 49087111..82eba6c5 100644 --- a/tests/factories/recipeFactory.js +++ b/tests/factories/recipeFactory.js @@ -25,6 +25,10 @@ export class RecipeFactory extends Factory { actionArgs = ConsoleLogArgumentFactory.build(); } + if (actionName === "multi-preference-experiment") { + actionArgs = MultiPreferenceFactory.build(); + } + this.data.latest_revision = { ...this.data.latest_revision, arguments: actionArgs, @@ -93,3 +97,52 @@ class ConsoleLogArgumentFactory extends Factory { return { message: new Field(faker.lorem.words) }; } } + +export class MultiPreferenceFactory extends Factory { + getFields() { + return { + branches: [], + experimentDocumentUrl: new Field(faker.internet.url), + slug: new Field(faker.lorem.slug), + userFacingDescription: new Field(faker.lorem.sentence), + userFacingName: new Field(faker.lorem.words), + }; + } +} + +export class MultiPrefBranchFactory extends Factory { + getFields() { + return { + slug: new Field(faker.lorem.slug), + ratio: new Field(faker.random.number), + preferences: [], + }; + } + + postGeneration() { + const { generatePreferenceCount } = this.options; + const preferences = {}; + const prefBranchOptions = ["user", "default"]; + const prefTypeOptions = ["integer", "string", "boolean"]; + const prefValueOptions = { + integer: faker.random.number(), + string: faker.random.word(), + boolean: faker.random.boolean(), + }; + + for (let i = 0; i < generatePreferenceCount; i++) { + const preferenceName = faker.lorem.slug(); + const preferenceBranchType = faker.random.arrayElement(prefBranchOptions); + const preferenceType = faker.random.arrayElement(prefTypeOptions); + const preferenceValue = prefValueOptions[preferenceType]; + const preference = { + preferenceBranchType, + preferenceType, + preferenceValue, + }; + preferences[preferenceName] = preference; + } + + this.data.preferences = preferences; + } +} From 5d3523bb04ff9d2172cc583afa2a83405c2dd5a0 Mon Sep 17 00:00:00 2001 From: tiftran Date: Wed, 24 Jun 2020 13:07:10 -0700 Subject: [PATCH 2/2] feedback changes and test changes --- .../components/pages/RecipeFormPage.js | 5 + .../recipes/form/RecipeFormHeader.js | 27 +-- tests/RecipeForm.test.js | 175 +++--------------- tests/jest.setup.js | 36 ++++ 4 files changed, 76 insertions(+), 167 deletions(-) diff --git a/extension/content/components/pages/RecipeFormPage.js b/extension/content/components/pages/RecipeFormPage.js index a79ea960..7279f8af 100644 --- a/extension/content/components/pages/RecipeFormPage.js +++ b/extension/content/components/pages/RecipeFormPage.js @@ -1,6 +1,7 @@ import React from "react"; import { useParams } from "react-router-dom"; +import { INITIAL_ACTION_ARGUMENTS } from "devtools/components/recipes/form/ActionArguments"; import RecipeForm from "devtools/components/recipes/form/RecipeForm"; import RecipeFormHeader from "devtools/components/recipes/form/RecipeFormHeader"; import { @@ -35,6 +36,10 @@ export default function RecipeFormPage() { .then(({ comment, action_name, ...recipeData }) => { setData({ ...recipeData, + arguments: { + ...INITIAL_ACTION_ARGUMENTS[action_name], + ...recipeData.arguments, + }, action: actions.find((a) => a.name === action_name), }); setImportInstructions(comment); diff --git a/extension/content/components/recipes/form/RecipeFormHeader.js b/extension/content/components/recipes/form/RecipeFormHeader.js index 24918bef..eeaa35a9 100644 --- a/extension/content/components/recipes/form/RecipeFormHeader.js +++ b/extension/content/components/recipes/form/RecipeFormHeader.js @@ -3,7 +3,6 @@ import React from "react"; import { useHistory, useParams } from "react-router-dom"; import { Alert, Button, Icon, IconButton, Input, Modal } from "rsuite"; -import { INITIAL_ACTION_ARGUMENTS } from "devtools/components/recipes/form/ActionArguments"; import { useEnvironmentState, useSelectedNormandyEnvironmentAPI, @@ -34,34 +33,18 @@ export default function RecipeFormHeader() { } }; - const isEnrollmentPausedRequired = (action, data) => { - const { name } = action; - const { arguments: args } = data; - const intitation_action_args = INITIAL_ACTION_ARGUMENTS[name]; - return ( - !("isEnrollmentPaused" in args) && - "isEnrollmentPaused" in intitation_action_args - ); - }; - const saveRecipe = (comment) => { const { action, ...cleanedData } = data; - let id; if (!history.location.pathname.includes("clone")) { id = recipeId; } - let saveData = { ...cleanedData, comment, action_id: action.id }; - - if (isEnrollmentPausedRequired(action, cleanedData)) { - saveData = { - ...saveData, - arguments: { ...saveData.arguments, isEnrollmentPaused: false }, - }; - } - - const requestSave = normandyApi.saveRecipe(id, saveData); + const requestSave = normandyApi.saveRecipe(id, { + ...cleanedData, + comment, + action_id: action.id, + }); requestSave .then((savedRecipe) => { diff --git a/tests/RecipeForm.test.js b/tests/RecipeForm.test.js index ab4edab8..947d03bc 100644 --- a/tests/RecipeForm.test.js +++ b/tests/RecipeForm.test.js @@ -6,9 +6,11 @@ import { within, } from "@testing-library/react"; import React from "react"; - import "@testing-library/jest-dom/extend-expect"; + import App from "devtools/components/App"; +import RecipeFormPage from "devtools/components/pages/RecipeFormPage"; +import ExperimenterAPI from "devtools/utils/experimenterApi"; import NormandyAPI from "devtools/utils/normandyApi"; import { ActionsResponse, FiltersFactory } from "./factories/filterFactory"; @@ -24,11 +26,7 @@ import { } from "./factories/recipeFactory"; describe("The `RecipeForm` component", () => { - afterEach(() => { - jest.clearAllMocks(); - cleanup(); - }); - + afterEach(cleanup); const findForm = (formGroups, formName) => { const forms = formGroups.filter((form) => within(form).queryByText(formName), @@ -141,7 +139,7 @@ describe("The `RecipeForm` component", () => { ); }; - const multiprefRecipeSetUp = () => { + const multiprefExperimenterRecipeSetUp = () => { const versions = VersionFilterObjectFactory.build( {}, { generateVersionsCount: 2 }, @@ -163,8 +161,8 @@ describe("The `RecipeForm` component", () => { ); const branches = [branch1, branch2]; const multiPrefArguments = MultiPreferenceFactory.build({ branches }); - recipe.latest_revision.arguments = multiPrefArguments; - + recipe.arguments = multiPrefArguments; + recipe.action_name = "multi-preference-experiment"; return recipe; }; @@ -313,7 +311,6 @@ describe("The `RecipeForm` component", () => { const recipeData = consoleLogRecipeSetup(); setup(recipeData); const { getByText, getAllByRole } = await render(); - await waitFor(() => { expect(getByText("Edit Recipe")).toBeInTheDocument(); }); @@ -475,137 +472,28 @@ describe("The `RecipeForm` component", () => { ); }); - it("should add missing isEnrollmentPaused to edited multipref experiment recipe ", async () => { - const recipeData = multiprefRecipeSetUp(); + it("should have isEnrollmentPaused set when import from experimenter", async () => { + const recipeData = multiprefExperimenterRecipeSetUp(); setup(recipeData); - const { getByText, getAllByRole } = await render(); - - fireEvent.click(getByText("Recipes")); - - await waitFor(() => { - expect(getByText("Edit Recipe")).toBeInTheDocument(); - }); - fireEvent.click(getByText("Edit Recipe")); - await waitFor(() => { - expect(getByText("Experimenter Slug")).toBeInTheDocument(); - }); - - let formGroups = getAllByRole("group"); - - const userFacingForm = findForm(formGroups, "User Facing Name"); - const highVolumeForm = findForm(formGroups, "High Volume Recipe"); - const BranchForm = findForm(formGroups, "Branches"); - - const userFacingInput = userFacingForm.querySelector("input"); - const highVolumeToggle = within(highVolumeForm).getByRole("button"); - const firstDeleteBranchButton = BranchForm.querySelectorAll("button")[0]; - - const userFacingName = "A new user facing name"; - - fireEvent.change(userFacingInput, { target: { value: userFacingName } }); - fireEvent.click(highVolumeToggle); - fireEvent.click(firstDeleteBranchButton); - - formGroups = getAllByRole("group"); - const preferences = findForm(formGroups, "Preferences"); - - const prefButtons = preferences.querySelectorAll("button"); - const prefDeleteButton = prefButtons[0]; - fireEvent.click(prefDeleteButton); - - fireEvent.click(getByText("Add Preference")); - - formGroups = getAllByRole("group"); - const prefForm = findForm(formGroups, "Preferences"); - - // find preference fields - let prefFieldForms = within(prefForm).getAllByRole("group"); - const prefNameForm = findForm(prefFieldForms, "Name"); - const prefBranchTypeForm = findForm( - prefFieldForms, - "Preference Branch Type", - ); - const prefTypeForm = findForm(prefFieldForms, "Preference Type"); - const prefNameInput = prefNameForm.querySelector("input"); - - const prefNameValue = "pref1"; - const prefBranchTypeValue = "Default"; - const prefTypeValue = "Integer"; - const prefValueValue = 22; - - fireEvent.change(prefNameInput, { target: { value: prefNameValue } }); - fireEvent.click(within(prefBranchTypeForm).getByRole("combobox")); - fireEvent.click(getByText(prefBranchTypeValue)); - fireEvent.click(within(prefTypeForm).getByRole("combobox")); - fireEvent.click(getByText(prefTypeValue)); - - prefFieldForms = within(prefForm).getAllByRole("group"); - const prefValueForm = findForm(prefFieldForms, "Value"); - const prefValueInput = prefValueForm.querySelector("input"); - - fireEvent.change(prefValueInput, { target: { value: prefValueValue } }); - - fireEvent.click(getByText("Save")); - - const modalDialog = getAllByRole("dialog")[0]; - const commentInput = modalDialog.querySelector("textArea"); - const saveMessage = "Edited Recipe"; - fireEvent.change(commentInput, { target: { value: saveMessage } }); - - fireEvent.click(within(modalDialog).getByText("Save")); + jest + .spyOn(ExperimenterAPI.prototype, "fetchRecipe") + .mockImplementation(() => Promise.resolve(recipeData)); + jest + .spyOn(NormandyAPI.prototype, "fetchAllActions") + .mockImplementation(() => + Promise.resolve([{ id: 1, name: "multi-preference-experiment" }]), + ); - const { latest_revision } = recipeData; - /* eslint-disable prefer-const */ - let { - action, - comment: _omitComment, - ...updatedRecipeData - } = latest_revision; - /* eslint-enable prefer-const */ - const { branches } = updatedRecipeData.arguments; - const branch = branches[1]; - const updatedBranches = [ + /* global renderWithContext */ + const { getByText, getAllByRole } = await renderWithContext( + , { - ...branch, - preferences: { - [prefNameValue]: { - preferenceBranchType: "default", - preferenceType: "integer", - preferenceValue: prefValueValue, - }, - }, + route: "/prod/recipes/import/experimenter-slug", + path: "/prod/recipes/import/:experimenterSlug", }, - ]; - updatedRecipeData = { - ...updatedRecipeData, - comment: saveMessage, - action_id: action.id, - arguments: { - ...updatedRecipeData.arguments, - userFacingName, - isHighPopulation: true, - isEnrollmentPaused: false, - branches: updatedBranches, - }, - }; - expect(NormandyAPI.prototype.saveRecipe).toBeCalledWith( - recipeData.id.toString(), - updatedRecipeData, ); - }); - - it("should leave isEnrollmentPaused when it's already set ", async () => { - const recipeData = multiprefRecipeSetUp(); - recipeData.latest_revision.arguments.isEnrollmentPaused = true; - setup(recipeData); - const { getByText, getAllByRole } = await render(); + expect(ExperimenterAPI.prototype.fetchRecipe).toHaveBeenCalled(); - fireEvent.click(getByText("Recipes")); - - await waitFor(() => { - expect(getByText("Edit Recipe")).toBeInTheDocument(); - }); - fireEvent.click(getByText("Edit Recipe")); await waitFor(() => { expect(getByText("Experimenter Slug")).toBeInTheDocument(); }); @@ -625,26 +513,23 @@ describe("The `RecipeForm` component", () => { fireEvent.click(within(modalDialog).getByText("Save")); - const { latest_revision } = recipeData; - /* eslint-disable prefer-const */ - let { - action, + /* eslint-disable prefer-const */ let { + action_name: _omitActionName, comment: _omitComment, ...updatedRecipeData - } = latest_revision; - /* eslint-enable prefer-const */ - - updatedRecipeData = { + } = recipeData; + /* eslint-enable prefer-const */ updatedRecipeData = { ...updatedRecipeData, comment: saveMessage, - action_id: action.id, + action_id: 1, arguments: { ...updatedRecipeData.arguments, isHighPopulation: true, + isEnrollmentPaused: false, }, }; expect(NormandyAPI.prototype.saveRecipe).toBeCalledWith( - recipeData.id.toString(), + undefined, updatedRecipeData, ); }); diff --git a/tests/jest.setup.js b/tests/jest.setup.js index aac61193..79035117 100644 --- a/tests/jest.setup.js +++ b/tests/jest.setup.js @@ -1,6 +1,14 @@ /* eslint-env node */ import crypto from "crypto"; +import { render } from "@testing-library/react"; +import { createMemoryHistory } from "history"; +import PropTypes from "prop-types"; +import React from "react"; +import { Router, Route } from "react-router-dom"; + +import { EnvironmentProvider } from "devtools/contexts/environment"; + Object.defineProperty(global.self, "crypto", { value: { getRandomValues: (arr) => crypto.randomBytes(arr.length), @@ -35,3 +43,31 @@ global.document.body.createTextRange = () => ({ getBoundingClientRect: () => {}, getClientRects: () => [], }); + +global.renderWithContext = ( + ui, + { + route = "/", + path = "/", + history = createMemoryHistory({ initialEntries: [route] }), + } = {}, +) => { + const Wrapper = ({ children }) => ( + + + {children} + + + ); + + Wrapper.propTypes = { + children: PropTypes.object, + }; + return { + ...render(ui, { wrapper: Wrapper }), + // adding `history` to the returned utilities to allow us + // to reference it in our tests (just try to avoid using + // this to test implementation details). + history, + }; +};