/src/v2/Components/Authentication/__tests__/Views/LoginForm.jest.tsx

https://github.com/artsy/force-public · TypeScript · 328 lines · 264 code · 62 blank · 2 comment · 10 complexity · 48610e2ef746b808204dc81a1d9b31d0 MD5 · raw file

  1. /* eslint-disable jest/no-done-callback */
  2. import { LoginForm } from "v2/Components/Authentication/Views/LoginForm"
  3. import { mount } from "enzyme"
  4. import { ChangeEvents, LoginValues } from "../fixtures"
  5. import { flushPromiseQueue } from "v2/DevTools"
  6. jest.mock("sharify", () => ({ data: { RECAPTCHA_KEY: "recaptcha-api-key" } }))
  7. // FIXME: mock Formik async and remove setTimeout
  8. describe("LoginForm", () => {
  9. let props
  10. beforeEach(() => {
  11. props = {
  12. handleSubmit: jest.fn(),
  13. onFacebookLogin: jest.fn(),
  14. onAppleLogin: jest.fn(),
  15. }
  16. window.grecaptcha.execute.mockClear()
  17. })
  18. const getWrapper = (passedProps = props) => {
  19. return mount(<LoginForm {...passedProps} />)
  20. }
  21. it("renders errors", async () => {
  22. const wrapper = getWrapper({ values: { email: "" } })
  23. wrapper.find("form").simulate("submit")
  24. await flushPromiseQueue()
  25. expect(wrapper.html()).toMatch("Please enter a valid email.")
  26. })
  27. it("clears error after input change", done => {
  28. const wrapper = getWrapper({
  29. values: { email: "" },
  30. error: "Some global server error",
  31. })
  32. const input = wrapper.find(`input[name="email"]`)
  33. expect((wrapper.state() as any).error).toEqual("Some global server error")
  34. input.simulate("change")
  35. wrapper.update()
  36. setTimeout(() => {
  37. expect((wrapper.state() as any).error).toEqual(null)
  38. done()
  39. })
  40. })
  41. it("renders spinner", done => {
  42. const wrapper = getWrapper({ values: LoginValues })
  43. const input = wrapper.find("form")
  44. input.simulate("submit")
  45. wrapper.update()
  46. setTimeout(() => {
  47. const submitButton = wrapper.find("Button")
  48. expect((submitButton.props() as any).loading).toEqual(true)
  49. done()
  50. })
  51. })
  52. describe("onSubmit", () => {
  53. it("calls handleSubmit with expected params", done => {
  54. props.values = LoginValues
  55. const wrapper = getWrapper()
  56. const input = wrapper.find("form")
  57. input.simulate("submit")
  58. wrapper.update()
  59. setTimeout(() => {
  60. expect(props.handleSubmit).toBeCalledWith(
  61. {
  62. email: "foo@bar.com",
  63. password: "password123", // pragma: allowlist secret
  64. otpRequired: false,
  65. },
  66. expect.anything()
  67. )
  68. done()
  69. })
  70. })
  71. it("calls handleSubmit with expected params when server requests a two-factor authentication code", async () => {
  72. const wrapper = getWrapper({
  73. ...props,
  74. values: { ...LoginValues, otp_attempt: "" },
  75. error: "missing two-factor authentication code",
  76. })
  77. wrapper
  78. .find("input[name='otp_attempt']")
  79. .simulate("change", ChangeEvents.otpAttempt)
  80. wrapper.find("form").simulate("submit")
  81. await flushPromiseQueue()
  82. wrapper.update()
  83. expect(props.handleSubmit).toBeCalledWith(
  84. {
  85. email: "foo@bar.com",
  86. password: "password123", // pragma: allowlist secret
  87. otp_attempt: "123456",
  88. otpRequired: true,
  89. },
  90. expect.anything()
  91. )
  92. })
  93. it("renders password error", async () => {
  94. ;(props.handleSubmit as jest.Mock).mockImplementation(
  95. (_values, actions) => {
  96. actions.setStatus({ error: "some password error" })
  97. }
  98. )
  99. const wrapper = getWrapper({
  100. ...props,
  101. values: { email: "", password: "" },
  102. })
  103. wrapper.find('input[name="email"]').simulate("change", ChangeEvents.email)
  104. wrapper
  105. .find('input[name="password"]')
  106. .simulate("change", ChangeEvents.password)
  107. wrapper.find("form").simulate("submit")
  108. await flushPromiseQueue()
  109. wrapper.update()
  110. expect(wrapper.html()).toMatch("some password error")
  111. expect(props.handleSubmit.mock.calls[0][0]).toEqual({
  112. email: "email@email.com",
  113. password: "password", // pragma: allowlist secret
  114. otpRequired: false,
  115. })
  116. })
  117. it("does not render email errors for social sign ups", done => {
  118. const wrapper = getWrapper()
  119. const socialLink = wrapper.find("Clickable").at(0)
  120. socialLink.simulate("click")
  121. wrapper.update()
  122. setTimeout(() => {
  123. expect(wrapper.html()).not.toMatch("Please enter a valid email.")
  124. done()
  125. })
  126. })
  127. it("shows 2FA one-time password prompt", async () => {
  128. ;(props.handleSubmit as jest.Mock).mockImplementation(
  129. (values, actions) => {
  130. if (!values.otp_attempt) {
  131. actions.setStatus({
  132. error: "missing two-factor authentication code",
  133. })
  134. } else if (values.otp_attempt !== "123456") {
  135. actions.setStatus({
  136. error: "invalid two-factor authentication code",
  137. })
  138. } else {
  139. actions.setStatus(null)
  140. }
  141. }
  142. )
  143. const wrapper = getWrapper({
  144. ...props,
  145. values: { email: "", password: "", otp_attempt: "" },
  146. })
  147. wrapper.find('input[name="email"]').simulate("change", ChangeEvents.email)
  148. wrapper
  149. .find('input[name="password"]')
  150. .simulate("change", ChangeEvents.password)
  151. const form = wrapper.find("form")
  152. form.simulate("submit")
  153. await flushPromiseQueue()
  154. wrapper.update()
  155. expect(wrapper.html()).not.toMatch(
  156. "missing two-factor authentication code"
  157. )
  158. expect(props.handleSubmit.mock.calls[0][0]).toEqual({
  159. email: "email@email.com",
  160. password: "password", // pragma: allowlist secret
  161. otpRequired: false,
  162. otp_attempt: "",
  163. })
  164. const inputOtp = wrapper.find('input[name="otp_attempt"]')
  165. inputOtp.simulate("change", ChangeEvents.invalidOtpAttempt)
  166. form.simulate("submit")
  167. await flushPromiseQueue()
  168. wrapper.update()
  169. expect(wrapper.html()).toMatch("invalid two-factor authentication code")
  170. expect(props.handleSubmit.mock.calls[1][0]).toEqual({
  171. email: "email@email.com",
  172. password: "password", // pragma: allowlist secret
  173. otp_attempt: "111111",
  174. otpRequired: true,
  175. })
  176. inputOtp.simulate("change", ChangeEvents.otpAttempt)
  177. form.simulate("submit")
  178. await flushPromiseQueue()
  179. wrapper.update()
  180. expect(wrapper.html()).not.toMatch("Error")
  181. expect(props.handleSubmit.mock.calls[2][0]).toEqual({
  182. email: "email@email.com",
  183. password: "password", // pragma: allowlist secret
  184. otp_attempt: "123456",
  185. otpRequired: true,
  186. })
  187. })
  188. it("shows on-demand one-time password prompt", async () => {
  189. ;(props.handleSubmit as jest.Mock).mockImplementation(
  190. (values, actions) => {
  191. if (!values.otp_attempt) {
  192. actions.setStatus({
  193. error: "missing on-demand authentication code",
  194. })
  195. } else if (values.otp_attempt !== "123456") {
  196. actions.setStatus({
  197. error: "invalid on-demand authentication code",
  198. })
  199. } else {
  200. actions.setStatus(null)
  201. }
  202. }
  203. )
  204. const wrapper = getWrapper({
  205. ...props,
  206. values: { email: "", password: "", otp_attempt: "" },
  207. })
  208. wrapper.find('input[name="email"]').simulate("change", ChangeEvents.email)
  209. wrapper
  210. .find('input[name="password"]')
  211. .simulate("change", ChangeEvents.password)
  212. const form = wrapper.find("form")
  213. form.simulate("submit")
  214. await flushPromiseQueue()
  215. wrapper.update()
  216. expect(wrapper.html()).not.toMatch(
  217. "missing on-demand authentication code"
  218. )
  219. expect(wrapper.html()).toMatch(
  220. "Your safety and security are important to us. Please check your email for a one-time authentication code to complete your login."
  221. )
  222. expect(props.handleSubmit.mock.calls[0][0]).toEqual({
  223. email: "email@email.com",
  224. password: "password", // pragma: allowlist secret
  225. otpRequired: false,
  226. otp_attempt: "",
  227. })
  228. expect((wrapper.state() as any).otpRequired).toEqual(true)
  229. const inputOtp = wrapper.find('input[name="otp_attempt"]')
  230. inputOtp.simulate("change", ChangeEvents.invalidOtpAttempt)
  231. form.simulate("submit")
  232. await flushPromiseQueue()
  233. wrapper.update()
  234. expect(wrapper.html()).toMatch("invalid on-demand authentication code")
  235. expect(props.handleSubmit.mock.calls[1][0]).toEqual({
  236. email: "email@email.com",
  237. password: "password", // pragma: allowlist secret
  238. otp_attempt: "111111",
  239. otpRequired: true,
  240. })
  241. inputOtp.simulate("change", ChangeEvents.otpAttempt)
  242. form.simulate("submit")
  243. await flushPromiseQueue()
  244. wrapper.update()
  245. expect(wrapper.html()).not.toMatch("Error")
  246. expect(props.handleSubmit.mock.calls[2][0]).toEqual({
  247. email: "email@email.com",
  248. password: "password", // pragma: allowlist secret
  249. otp_attempt: "123456",
  250. otpRequired: true,
  251. })
  252. })
  253. it("fires reCAPTCHA event", done => {
  254. props.values = LoginValues
  255. const wrapper = getWrapper()
  256. const input = wrapper.find("form")
  257. input.simulate("submit")
  258. setTimeout(() => {
  259. expect(window.grecaptcha.execute).toBeCalledWith("recaptcha-api-key", {
  260. action: "login_submit",
  261. })
  262. done()
  263. })
  264. })
  265. })
  266. })