preview-pane.test.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import React from 'react'
  2. import { screen, render, fireEvent } from '@testing-library/react'
  3. import PreviewPane from '../../../../../frontend/js/features/preview/components/preview-pane'
  4. const { expect } = require('chai')
  5. describe('<PreviewPane />', function() {
  6. const sampleError1 = {
  7. content: 'error 1 content',
  8. file: 'main.tex',
  9. level: 'error',
  10. line: 17,
  11. message: 'Misplaced alignment tab character &.'
  12. }
  13. const sampleError2 = {
  14. content: 'error 1 content',
  15. file: 'main.tex',
  16. level: 'error',
  17. line: 22,
  18. message: 'Extra alignment tab has been changed to cr.'
  19. }
  20. const sampleWarning = {
  21. file: 'main.tex',
  22. level: 'warning',
  23. line: 30,
  24. message: "Reference `idontexist' on page 1 undefined on input line 30."
  25. }
  26. describe('first error pop-up', function() {
  27. it('renders a first error pop-up with the first error', function() {
  28. const propsAfterCompileWithErrors = getProps(false, {
  29. errors: [sampleError1, sampleError2],
  30. warnings: [sampleWarning]
  31. })
  32. render(<PreviewPane {...propsAfterCompileWithErrors} />)
  33. screen.getByRole('alertdialog', {
  34. name: 'This project has errors. This is the first one.'
  35. })
  36. screen.getByText(sampleError1.message)
  37. })
  38. it('does not render a first error pop-up when there are only warnings', function() {
  39. const propsAfterCompileWithWarningsOnly = getProps(false, {
  40. errors: [],
  41. warnings: [sampleWarning]
  42. })
  43. render(<PreviewPane {...propsAfterCompileWithWarningsOnly} />)
  44. expect(
  45. screen.queryByRole('alertdialog', {
  46. name: 'This project has errors. This is the first one.'
  47. })
  48. ).to.not.exist
  49. })
  50. it('does not render a first error pop-up when a compile is ongoing', function() {
  51. const propsWhileCompiling = getProps(true, {
  52. errors: [sampleError1, sampleError2],
  53. warnings: [sampleWarning]
  54. })
  55. render(<PreviewPane {...propsWhileCompiling} />)
  56. expect(
  57. screen.queryByRole('alertdialog', {
  58. name: 'This project has errors. This is the first one.'
  59. })
  60. ).to.not.exist
  61. })
  62. it('does not render a first error pop-up when viewing logs', function() {
  63. const propsWithErrorsViewingLogs = getProps(
  64. false,
  65. {
  66. errors: [sampleError1, sampleError2],
  67. warnings: [sampleWarning]
  68. },
  69. Date.now(),
  70. true
  71. )
  72. render(<PreviewPane {...propsWithErrorsViewingLogs} />)
  73. expect(
  74. screen.queryByRole('alertdialog', {
  75. name: 'This project has errors. This is the first one.'
  76. })
  77. ).to.not.exist
  78. })
  79. it('does not render a first error pop-up when going back to the PDF view after viewing logs', function() {
  80. const nowTimestamp = Date.now()
  81. const propsWithErrorsViewingLogs = getProps(
  82. false,
  83. {
  84. errors: [sampleError1, sampleError2],
  85. warnings: [sampleWarning]
  86. },
  87. nowTimestamp,
  88. true
  89. )
  90. const propsWithErrorsAfterViewingLogs = getProps(
  91. false,
  92. {
  93. errors: [sampleError1, sampleError2],
  94. warnings: [sampleWarning]
  95. },
  96. nowTimestamp,
  97. false
  98. )
  99. const { rerender } = render(
  100. <PreviewPane {...propsWithErrorsViewingLogs} />
  101. )
  102. rerender(<PreviewPane {...propsWithErrorsAfterViewingLogs} />)
  103. expect(
  104. screen.queryByRole('alertdialog', {
  105. name: 'This project has errors. This is the first one.'
  106. })
  107. ).to.not.exist
  108. })
  109. it('renders a first error pop-up with updated errors after recompiling', function() {
  110. const nowTimestamp = Date.now()
  111. const laterTimestamp = Date.now() + 1000
  112. const propsWithErrorsAfterFirstCompile = getProps(
  113. false,
  114. {
  115. errors: [sampleError1, sampleError2],
  116. warnings: [sampleWarning]
  117. },
  118. nowTimestamp,
  119. true
  120. )
  121. const propsWithErrorsAfterSecondCompile = getProps(
  122. false,
  123. {
  124. errors: [sampleError2],
  125. warnings: [sampleWarning]
  126. },
  127. laterTimestamp,
  128. false
  129. )
  130. const { rerender } = render(
  131. <PreviewPane {...propsWithErrorsAfterFirstCompile} />
  132. )
  133. rerender(<PreviewPane {...propsWithErrorsAfterSecondCompile} />)
  134. screen.getByRole('alertdialog', {
  135. name: 'This project has errors. This is the first one.'
  136. })
  137. screen.getByText(sampleError2.message)
  138. })
  139. it('allows dismissing the first error pop-up', function() {
  140. const propsWithErrors = getProps(false, {
  141. errors: [sampleError1, sampleError2],
  142. warnings: [sampleWarning]
  143. })
  144. render(<PreviewPane {...propsWithErrors} />)
  145. const dismissPopUpButton = screen.getByRole('button', {
  146. name: 'Dismiss first error alert'
  147. })
  148. fireEvent.click(dismissPopUpButton)
  149. expect(
  150. screen.queryByRole('alertdialog', {
  151. name: 'This project has errors. This is the first one.'
  152. })
  153. ).to.not.exist
  154. })
  155. it('does not render the first error pop-up with new recompiles after it being dismissed once', function() {
  156. const nowTimestamp = Date.now()
  157. const laterTimestamp = Date.now() + 1000
  158. const propsWithErrorsForFirstCompile = getProps(
  159. false,
  160. {
  161. errors: [sampleError1, sampleError2],
  162. warnings: [sampleWarning]
  163. },
  164. nowTimestamp
  165. )
  166. const propsWithErrorsForSecondCompile = getProps(
  167. false,
  168. {
  169. errors: [sampleError2],
  170. warnings: [sampleWarning]
  171. },
  172. laterTimestamp
  173. )
  174. const { rerender } = render(
  175. <PreviewPane {...propsWithErrorsForFirstCompile} />
  176. )
  177. const dismissPopUpButton = screen.getByRole('button', {
  178. name: 'Dismiss first error alert'
  179. })
  180. fireEvent.click(dismissPopUpButton)
  181. rerender(<PreviewPane {...propsWithErrorsForSecondCompile} />)
  182. expect(
  183. screen.queryByRole('alertdialog', {
  184. name: 'This project has errors. This is the first one.'
  185. })
  186. ).to.not.exist
  187. })
  188. })
  189. describe('accessible description of the compile result', function() {
  190. it('renders an accessible description with the errors and warnings count', function() {
  191. const errors = [sampleError1, sampleError2]
  192. const warnings = [sampleWarning]
  193. const propsWithErrorsAndWarnings = getProps(false, {
  194. errors,
  195. warnings
  196. })
  197. render(<PreviewPane {...propsWithErrorsAndWarnings} />)
  198. screen.getByText(`${errors.length} error${errors.length > 1 ? 's' : ''}`)
  199. screen.getByText(
  200. `${warnings.length} warning${warnings.length > 1 ? 's' : ''}`
  201. )
  202. })
  203. it('renders an accessible description for failed compiles with CLSI errors', function() {
  204. const sampleCLSIError = {
  205. clsiMaintenance: true
  206. }
  207. const propsWithCLSIError = getProps(
  208. false,
  209. {},
  210. Date.now(),
  211. false,
  212. true,
  213. {},
  214. sampleCLSIError
  215. )
  216. render(<PreviewPane {...propsWithCLSIError} />)
  217. screen.getByText('This project did not compile because of an error')
  218. })
  219. it('renders an accessible description for failed compiles with validation issues', function() {
  220. const sampleValidationIssue = {
  221. clsiMaintenance: true
  222. }
  223. const propsWithValidationIssue = getProps(
  224. false,
  225. {},
  226. Date.now(),
  227. false,
  228. true,
  229. sampleValidationIssue,
  230. {}
  231. )
  232. render(<PreviewPane {...propsWithValidationIssue} />)
  233. screen.getByText(
  234. 'This project did not compile because of a validation issue'
  235. )
  236. })
  237. })
  238. function getProps(
  239. isCompiling = false,
  240. logEntries = {},
  241. lastCompileTimestamp = Date.now(),
  242. isShowingLogs = false,
  243. compileFailed = false,
  244. validationIssues = {},
  245. errors = {}
  246. ) {
  247. const logEntriesWithDefaults = {
  248. errors: [],
  249. warnings: [],
  250. typesetting: [],
  251. ...logEntries
  252. }
  253. logEntriesWithDefaults.all = [
  254. ...logEntriesWithDefaults.errors,
  255. ...logEntriesWithDefaults.warnings,
  256. ...logEntriesWithDefaults.typesetting
  257. ]
  258. return {
  259. compilerState: {
  260. autoCompileHasChanges: false,
  261. isAutoCompileOn: false,
  262. isCompiling: isCompiling,
  263. isClearingCache: false,
  264. isDraftModeOn: false,
  265. isSyntaxCheckOn: false,
  266. lastCompileTimestamp,
  267. logEntries: logEntriesWithDefaults,
  268. compileFailed,
  269. validationIssues,
  270. errors
  271. },
  272. onClearCache: () => {},
  273. onLogEntryLocationClick: () => {},
  274. onRecompile: () => {},
  275. onRecompileFromScratch: () => {},
  276. onRunSyntaxCheckNow: () => {},
  277. onSetAutoCompile: () => {},
  278. onSetDraftMode: () => {},
  279. onSetSyntaxCheck: () => {},
  280. onToggleLogs: () => {},
  281. onSetSplitLayout: () => {},
  282. onSetFullLayout: () => {},
  283. onStopCompilation: () => {},
  284. showLogs: isShowingLogs,
  285. splitLayout: true
  286. }
  287. }
  288. })