preview-logs-pane.test.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import { screen, fireEvent } from '@testing-library/react'
  2. import PreviewLogsPane from '../../../../../frontend/js/features/preview/components/preview-logs-pane'
  3. import sinon from 'sinon'
  4. import { renderWithEditorContext } from '../../../helpers/render-with-context'
  5. const { expect } = require('chai')
  6. describe('<PreviewLogsPane />', function () {
  7. const sampleError1 = {
  8. content: 'error 1 content',
  9. file: 'main.tex',
  10. level: 'error',
  11. line: 17,
  12. message: 'Misplaced alignment tab character &.',
  13. }
  14. const sampleError2 = {
  15. content: 'error 1 content',
  16. file: 'main.tex',
  17. level: 'error',
  18. line: 22,
  19. message: 'Extra alignment tab has been changed to cr.',
  20. }
  21. const sampleWarning = {
  22. file: 'main.tex',
  23. level: 'warning',
  24. line: 30,
  25. message: "Reference `idontexist' on page 1 undefined on input line 30.",
  26. }
  27. const sampleTypesettingIssue = {
  28. file: 'main.tex',
  29. level: 'typesetting',
  30. line: 12,
  31. message: "Reference `idontexist' on page 1 undefined on input line 30.",
  32. }
  33. const sampleRawLog = `
  34. This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex 2020.9.10) 6 NOV 2020 15:23
  35. entering extended mode
  36. \\write18 enabled.
  37. %&-line parsing enabled.
  38. **main.tex
  39. (/compile/main.tex
  40. LaTeX2e <2020-02-02> patch level 5
  41. L3 programming layer <2020-07-17> (/usr/local/texlive/2020/texmf-dist/tex/latex
  42. /base/article.cls
  43. Document Class: article 2019/12/20 v1.4l Standard LaTeX document class
  44. (/usr/local/texlive/2020/texmf-dist/tex/latex/base/size10.clo
  45. File: size10.clo 2019/12/20 v1.4l Standard LaTeX file (size option)
  46. )`
  47. const errors = [sampleError1, sampleError2]
  48. const warnings = [sampleWarning]
  49. const typesetting = [sampleTypesettingIssue]
  50. const logEntries = {
  51. all: [...errors, ...warnings, ...typesetting],
  52. errors,
  53. warnings,
  54. typesetting,
  55. }
  56. const noOp = () => {}
  57. const onLogEntryLocationClick = sinon.stub()
  58. describe('with logs', function () {
  59. beforeEach(function () {
  60. renderWithEditorContext(
  61. <PreviewLogsPane
  62. logEntries={logEntries}
  63. rawLog={sampleRawLog}
  64. onLogEntryLocationClick={onLogEntryLocationClick}
  65. onClearCache={noOp}
  66. />
  67. )
  68. })
  69. it('renders all log entries with appropriate labels', function () {
  70. const errorEntries = screen.getAllByLabelText(
  71. `Log entry with level: error`
  72. )
  73. const warningEntries = screen.getAllByLabelText(
  74. `Log entry with level: warning`
  75. )
  76. const typesettingEntries = screen.getAllByLabelText(
  77. `Log entry with level: typesetting`
  78. )
  79. expect(errorEntries).to.have.lengthOf(errors.length)
  80. expect(warningEntries).to.have.lengthOf(warnings.length)
  81. expect(typesettingEntries).to.have.lengthOf(typesetting.length)
  82. })
  83. it('renders the raw log', function () {
  84. screen.getByLabelText('Raw logs from the LaTeX compiler')
  85. })
  86. it('renders a link to location button for every error and warning log entry', function () {
  87. logEntries.all.forEach((entry, index) => {
  88. const linkToSourceButton = screen.getByRole('button', {
  89. name: `Navigate to log position in source code: ${entry.file}, ${entry.line}`,
  90. })
  91. fireEvent.click(linkToSourceButton)
  92. expect(onLogEntryLocationClick).to.have.callCount(index + 1)
  93. const call = onLogEntryLocationClick.getCall(index)
  94. expect(
  95. call.calledWith({
  96. file: entry.file,
  97. line: entry.line,
  98. column: entry.column,
  99. })
  100. ).to.be.true
  101. })
  102. })
  103. it(' does not render a link to location button for the raw log entry', function () {
  104. const rawLogEntry = screen.getByLabelText(
  105. 'Raw logs from the LaTeX compiler'
  106. )
  107. expect(rawLogEntry.querySelector('.log-entry-header-link')).to.not.exist
  108. })
  109. })
  110. describe('with over 100 log entries', function () {
  111. it('renders only 100 with a warning message', function () {
  112. const errors = Array(200).fill(sampleError1)
  113. const logEntries = {
  114. all: [...errors, ...warnings, ...typesetting],
  115. errors,
  116. warnings,
  117. typesetting,
  118. }
  119. renderWithEditorContext(
  120. <PreviewLogsPane
  121. logEntries={logEntries}
  122. rawLog={sampleRawLog}
  123. onLogEntryLocationClick={onLogEntryLocationClick}
  124. onClearCache={noOp}
  125. />
  126. )
  127. const displayedLogEntries = screen.getAllByLabelText(
  128. `Log entry with level`,
  129. { exact: false }
  130. )
  131. screen.getByLabelText(`Maximum log entries limit hit`)
  132. // should only show the first 100 errors and stop
  133. expect(displayedLogEntries).to.have.lengthOf(100)
  134. })
  135. })
  136. describe('with validation issues', function () {
  137. const sampleValidationIssues = {
  138. sizeCheck: {
  139. resources: [
  140. { path: 'foo/bar', kbSize: 76221 },
  141. { path: 'bar/baz', kbSize: 2342 },
  142. ],
  143. },
  144. mainFile: true,
  145. }
  146. it('renders a validation entry for known issues', function () {
  147. renderWithEditorContext(
  148. <PreviewLogsPane
  149. validationIssues={sampleValidationIssues}
  150. onLogEntryLocationClick={onLogEntryLocationClick}
  151. onClearCache={noOp}
  152. />
  153. )
  154. const validationEntries = screen.getAllByLabelText(
  155. 'A validation issue which prevented this project from compiling'
  156. )
  157. expect(validationEntries).to.have.lengthOf(
  158. Object.keys(sampleValidationIssues).length
  159. )
  160. })
  161. it('ignores unknown issues', function () {
  162. renderWithEditorContext(
  163. <PreviewLogsPane
  164. validationIssues={{ unknownIssue: true }}
  165. onLogEntryLocationClick={onLogEntryLocationClick}
  166. onClearCache={noOp}
  167. />
  168. )
  169. const validationEntries = screen.queryAllByLabelText(
  170. 'A validation issue prevented this project from compiling'
  171. )
  172. expect(validationEntries).to.have.lengthOf(0)
  173. })
  174. })
  175. describe('with compilation errors', function () {
  176. const sampleErrors = {
  177. clsiMaintenance: true,
  178. tooRecentlyCompiled: true,
  179. compileTerminated: true,
  180. }
  181. it('renders an error entry for known errors', function () {
  182. renderWithEditorContext(
  183. <PreviewLogsPane
  184. errors={sampleErrors}
  185. onLogEntryLocationClick={onLogEntryLocationClick}
  186. onClearCache={noOp}
  187. />
  188. )
  189. const errorEntries = screen.getAllByLabelText(
  190. 'An error which prevented this project from compiling'
  191. )
  192. expect(errorEntries).to.have.lengthOf(Object.keys(sampleErrors).length)
  193. })
  194. it('ignores unknown errors', function () {
  195. renderWithEditorContext(
  196. <PreviewLogsPane
  197. errors={{ unknownIssue: true }}
  198. onLogEntryLocationClick={onLogEntryLocationClick}
  199. onClearCache={noOp}
  200. />
  201. )
  202. const errorEntries = screen.queryAllByLabelText(
  203. 'There was an error compiling this project'
  204. )
  205. expect(errorEntries).to.have.lengthOf(0)
  206. })
  207. })
  208. describe('with failing code check', function () {
  209. beforeEach(function () {
  210. renderWithEditorContext(
  211. <PreviewLogsPane
  212. logEntries={logEntries}
  213. rawLog={sampleRawLog}
  214. onLogEntryLocationClick={onLogEntryLocationClick}
  215. onClearCache={noOp}
  216. autoCompileHasLintingError
  217. />
  218. )
  219. })
  220. it('renders a code check failed entry', function () {
  221. screen.getByText(
  222. 'Your code has errors that need to be fixed before the auto-compile can run'
  223. )
  224. })
  225. })
  226. })