editor-left-menu.spec.tsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. import EditorLeftMenu from '../../../../frontend/js/features/editor-left-menu/components/editor-left-menu'
  2. import {
  3. AllowedImageName,
  4. OverallThemeMeta,
  5. SpellCheckLanguage,
  6. } from '../../../../types/project-settings'
  7. import { EditorProviders } from '../../helpers/editor-providers'
  8. import { mockScope } from './scope'
  9. import { Folder } from '../../../../types/folder'
  10. import { docsInFolder } from '@/features/file-tree/util/docs-in-folder'
  11. import getMeta from '@/utils/meta'
  12. describe('<EditorLeftMenu />', function () {
  13. beforeEach(function () {
  14. cy.viewport(800, 800)
  15. cy.interceptCompile()
  16. })
  17. describe('for non-anonymous users', function () {
  18. const overallThemes: OverallThemeMeta[] = [
  19. {
  20. name: 'Overall Theme 1',
  21. val: '',
  22. path: 'https://overleaf.com/overalltheme-1.css',
  23. },
  24. {
  25. name: 'Overall Theme 2',
  26. val: 'light-',
  27. path: 'https://overleaf.com/overalltheme-2.css',
  28. },
  29. ]
  30. const allowedImageNames: AllowedImageName[] = [
  31. {
  32. imageDesc: 'Image 1',
  33. imageName: 'img-1',
  34. },
  35. {
  36. imageDesc: 'Image 2',
  37. imageName: 'img-2',
  38. },
  39. ]
  40. beforeEach(function () {
  41. window.metaAttributesCache.set('ol-overallThemes', overallThemes)
  42. window.metaAttributesCache.set('ol-allowedImageNames', allowedImageNames)
  43. window.metaAttributesCache.set('ol-anonymous', false)
  44. window.metaAttributesCache.set('ol-gitBridgeEnabled', true)
  45. window.metaAttributesCache.set('ol-showSupport', true)
  46. Object.assign(getMeta('ol-ExposedSettings'), { ieeeBrandId: 123 })
  47. window.metaAttributesCache.set('ol-user', {
  48. email: 'sherlock@holmes.co.uk',
  49. first_name: 'Sherlock',
  50. last_name: 'Holmes',
  51. })
  52. })
  53. it('render full menu', function () {
  54. const scope = mockScope({
  55. ui: {
  56. leftMenuShown: true,
  57. },
  58. })
  59. cy.mount(
  60. <EditorProviders scope={scope}>
  61. <EditorLeftMenu />
  62. </EditorProviders>
  63. )
  64. // Download Menu
  65. cy.findByRole('heading', { name: 'Download' })
  66. cy.findByRole('link', { name: 'Source' })
  67. cy.findByRole('link', { name: 'PDF' })
  68. // Actions Menu
  69. cy.findByRole('heading', { name: 'Actions' })
  70. cy.findByRole('button', { name: 'Copy Project' })
  71. cy.findByRole('button', { name: 'Word Count' })
  72. // Sync Menu
  73. cy.findByRole('heading', { name: 'Sync' })
  74. cy.findByRole('button', { name: 'Dropbox' })
  75. cy.findByRole('button', { name: 'Git' })
  76. cy.findByRole('button', { name: 'GitHub' })
  77. // Settings Menu
  78. cy.findByRole('heading', { name: 'Settings' })
  79. cy.findByLabelText('Compiler')
  80. cy.findByLabelText('TeX Live version')
  81. cy.findByLabelText('Main document')
  82. cy.findByLabelText('Spell check')
  83. cy.findByLabelText('Auto-complete')
  84. cy.findByLabelText('Auto-close brackets')
  85. cy.findByLabelText('Code check')
  86. cy.findByLabelText('Editor theme')
  87. cy.findByLabelText('Overall theme')
  88. cy.findByLabelText('Keybindings')
  89. cy.findByLabelText('Font Size')
  90. cy.findByLabelText('Font Family')
  91. cy.findByLabelText('Line Height')
  92. cy.findByLabelText('PDF Viewer')
  93. // Help Menu
  94. cy.findByRole('heading', { name: 'Help' })
  95. cy.findByRole('button', { name: 'Show Hotkeys' })
  96. cy.findByRole('link', { name: 'Documentation' })
  97. cy.findByRole('button', { name: 'Contact Us' })
  98. })
  99. describe('download menu', function () {
  100. it('have a correct source & pdf download url', function () {
  101. const scope = mockScope({
  102. ui: {
  103. leftMenuShown: true,
  104. },
  105. })
  106. cy.mount(
  107. <EditorProviders scope={scope}>
  108. <EditorLeftMenu />
  109. </EditorProviders>
  110. )
  111. cy.findByRole('link', { name: 'Source' }).should(
  112. 'have.attr',
  113. 'href',
  114. '/project/project123/download/zip'
  115. )
  116. cy.findByRole('link', { name: 'PDF' })
  117. .should('have.attr', 'href')
  118. .and('match', /\/download\/project\/project123\/build/)
  119. })
  120. })
  121. describe('actions menu', function () {
  122. it('shows copy project modal correctly', function () {
  123. cy.intercept('POST', '/project/*/clone', {
  124. body: {
  125. project_id: 'new_project_id',
  126. },
  127. })
  128. const scope = mockScope({
  129. ui: {
  130. leftMenuShown: true,
  131. },
  132. })
  133. cy.mount(
  134. <EditorProviders scope={scope}>
  135. <EditorLeftMenu />
  136. </EditorProviders>
  137. )
  138. cy.findByRole('button', { name: 'Copy Project' }).click()
  139. cy.findByRole('heading', { name: 'Copy Project' })
  140. // try closing & re-opening the modal with different methods
  141. cy.findByRole('button', { name: 'Close' }).click()
  142. cy.findByRole('button', { name: 'Copy Project' }).click()
  143. cy.findByRole('button', { name: 'Cancel' }).click()
  144. cy.findByRole('button', { name: 'Copy Project' }).click()
  145. cy.findByLabelText('New Name').focus()
  146. cy.findByLabelText('New Name').clear()
  147. cy.findByLabelText('New Name').type('Project Renamed')
  148. cy.get('#clone-project-form-name[value="Project Renamed"')
  149. })
  150. it('shows word count modal correctly', function () {
  151. cy.intercept('GET', '/project/*/wordcount*', {
  152. texcount: {
  153. encode: 'ascii',
  154. textWords: 781,
  155. headWords: 66,
  156. outside: 11,
  157. headers: 41,
  158. elements: 2,
  159. mathInline: 6,
  160. mathDisplay: 1,
  161. errors: 0,
  162. },
  163. }).as('wordCount')
  164. const scope = mockScope({
  165. ui: {
  166. leftMenuShown: true,
  167. },
  168. })
  169. cy.mount(
  170. <EditorProviders scope={scope}>
  171. <EditorLeftMenu />
  172. </EditorProviders>
  173. )
  174. cy.findByRole('button', { name: 'Word Count' }).click()
  175. cy.wait('@wordCount')
  176. cy.findByText('Total Words:')
  177. cy.findByText('781')
  178. cy.findByText('Headers:')
  179. cy.findByText('41')
  180. cy.findByText('Math Inline:')
  181. cy.findByText('6')
  182. cy.findByText('Math Display:')
  183. cy.findByText('1')
  184. })
  185. })
  186. describe('sync menu', function () {
  187. it('shows dropbox modal correctly', function () {
  188. cy.intercept('GET', '/dropbox/status', {
  189. registered: true,
  190. })
  191. const scope = mockScope({
  192. ui: {
  193. leftMenuShown: true,
  194. },
  195. project: {
  196. members: [],
  197. owner: {
  198. _id: '123',
  199. },
  200. },
  201. user: {
  202. features: {
  203. dropbox: false,
  204. },
  205. },
  206. })
  207. cy.mount(
  208. <EditorProviders scope={scope}>
  209. <EditorLeftMenu />
  210. </EditorProviders>
  211. )
  212. cy.findByRole('button', { name: 'Dropbox' }).click()
  213. cy.findByText('Dropbox Sync')
  214. })
  215. it('shows git modal correctly', function () {
  216. const scope = mockScope({
  217. ui: {
  218. leftMenuShown: true,
  219. },
  220. project: {
  221. owner: {
  222. _id: '123',
  223. },
  224. features: {
  225. gitBridge: true,
  226. },
  227. },
  228. })
  229. cy.mount(
  230. <EditorProviders scope={scope}>
  231. <EditorLeftMenu />
  232. </EditorProviders>
  233. )
  234. cy.findByRole('button', { name: 'Git' }).click()
  235. cy.findByText('Clone with Git')
  236. cy.findByText(/clone your project by using the link below/)
  237. })
  238. it('shows git modal paywall correctly', function () {
  239. const scope = mockScope({
  240. ui: {
  241. leftMenuShown: true,
  242. },
  243. project: {
  244. owner: {
  245. _id: '123',
  246. },
  247. features: {
  248. gitBridge: false,
  249. },
  250. },
  251. })
  252. cy.mount(
  253. <EditorProviders scope={scope}>
  254. <EditorLeftMenu />
  255. </EditorProviders>
  256. )
  257. cy.findByRole('button', { name: 'Git' }).click()
  258. cy.findByText('Collaborate online and offline, using your own workflow')
  259. })
  260. it('shows github modal correctly', function () {
  261. cy.intercept('GET', '/user/github-sync/status', {
  262. available: false,
  263. enabled: false,
  264. }).as('user-status')
  265. cy.intercept('GET', '/project/*/github-sync/status', {
  266. enabled: false,
  267. }).as('project-status')
  268. const scope = mockScope({
  269. ui: {
  270. leftMenuShown: true,
  271. },
  272. })
  273. cy.mount(
  274. <EditorProviders scope={scope}>
  275. <EditorLeftMenu />
  276. </EditorProviders>
  277. )
  278. cy.wait('@compile')
  279. cy.findByRole('button', { name: 'GitHub' }).click()
  280. cy.findByText('GitHub Sync')
  281. cy.wait(['@user-status', '@project-status'])
  282. cy.findByText('Push to GitHub, pull to Overleaf')
  283. })
  284. it('hides the entire sync section when git bridge is disabled', function () {
  285. window.metaAttributesCache.set('ol-gitBridgeEnabled', false)
  286. cy.findByRole('button', { name: 'Dropbox' }).should('not.exist')
  287. cy.findByRole('button', { name: 'Git' }).should('not.exist')
  288. cy.findByRole('button', { name: 'GitHub' }).should('not.exist')
  289. })
  290. })
  291. describe('settings menu', function () {
  292. it('shows compiler menu correctly', function () {
  293. const scope = mockScope({
  294. ui: {
  295. leftMenuShown: true,
  296. },
  297. })
  298. cy.mount(
  299. <EditorProviders scope={scope}>
  300. <EditorLeftMenu />
  301. </EditorProviders>
  302. )
  303. cy.get<HTMLOptionElement>('#settings-menu-compiler option').then(
  304. options => {
  305. const values = [...options].map(o => o.value)
  306. expect(values).to.deep.eq([
  307. 'pdflatex',
  308. 'latex',
  309. 'xelatex',
  310. 'lualatex',
  311. ])
  312. const texts = [...options].map(o => o.text)
  313. expect(texts).to.deep.eq([
  314. 'pdfLaTeX',
  315. 'LaTeX',
  316. 'XeLaTeX',
  317. 'LuaLaTeX',
  318. ])
  319. }
  320. )
  321. })
  322. it('shows texlive version menu correctly', function () {
  323. const scope = mockScope({
  324. ui: {
  325. leftMenuShown: true,
  326. },
  327. })
  328. cy.mount(
  329. <EditorProviders scope={scope}>
  330. <EditorLeftMenu />
  331. </EditorProviders>
  332. )
  333. cy.get<HTMLOptionElement>('#settings-menu-imageName option').then(
  334. options => {
  335. const values = [...options].map(o => o.value)
  336. expect(values).to.deep.eq(['img-1', 'img-2'])
  337. const texts = [...options].map(o => o.text)
  338. expect(texts).to.deep.eq(['Image 1', 'Image 2'])
  339. }
  340. )
  341. })
  342. it('shows document menu correctly', function () {
  343. const rootFolder: Folder = {
  344. _id: 'root-folder-id',
  345. name: 'rootFolder',
  346. docs: [
  347. {
  348. _id: 'id1',
  349. name: 'main.tex',
  350. },
  351. {
  352. _id: 'id2',
  353. name: 'main2.tex',
  354. },
  355. ],
  356. fileRefs: [],
  357. folders: [],
  358. }
  359. const scope = mockScope({
  360. ui: {
  361. leftMenuShown: true,
  362. },
  363. })
  364. cy.mount(
  365. <EditorProviders scope={scope} rootFolder={[rootFolder as any]}>
  366. <EditorLeftMenu />
  367. </EditorProviders>
  368. )
  369. const docs = docsInFolder(rootFolder)
  370. cy.get<HTMLOptionElement>('#settings-menu-rootDocId option').then(
  371. options => {
  372. const values = [...options].map(o => o.value)
  373. expect(values).to.deep.eq(docs.map(doc => doc.doc.id))
  374. const texts = [...options].map(o => o.text)
  375. expect(texts).to.deep.eq(docs.map(doc => doc.path))
  376. }
  377. )
  378. })
  379. it('shows spellcheck menu correctly', function () {
  380. const languages: SpellCheckLanguage[] = [
  381. {
  382. name: 'Lang 1',
  383. code: 'lang-1',
  384. dic: 'lang_1',
  385. },
  386. {
  387. name: 'Lang 2',
  388. code: 'lang-2',
  389. dic: 'lang_2',
  390. },
  391. ]
  392. window.metaAttributesCache.set('ol-languages', languages)
  393. const scope = mockScope({
  394. ui: {
  395. leftMenuShown: true,
  396. },
  397. })
  398. cy.mount(
  399. <EditorProviders scope={scope}>
  400. <EditorLeftMenu />
  401. </EditorProviders>
  402. )
  403. cy.get<HTMLOptionElement>(
  404. '#settings-menu-spellCheckLanguage option'
  405. ).then(options => {
  406. const values = [...options].map(o => o.value)
  407. expect(values).to.deep.eq(['', 'lang-1', 'lang-2'])
  408. const texts = [...options].map(o => o.text)
  409. expect(texts).to.deep.eq(['Off', 'Lang 1', 'Lang 2'])
  410. })
  411. })
  412. it('shows dictionary modal correctly', function () {
  413. const scope = mockScope({
  414. ui: {
  415. leftMenuShown: true,
  416. },
  417. })
  418. cy.mount(
  419. <EditorProviders scope={scope}>
  420. <EditorLeftMenu />
  421. </EditorProviders>
  422. )
  423. cy.get('label[for="dictionary-settings"] ~ button').click()
  424. cy.findByText('Edit Dictionary')
  425. cy.findByText('Your custom dictionary is empty.')
  426. })
  427. it('shows auto-complete menu correctly', function () {
  428. const scope = mockScope({
  429. ui: {
  430. leftMenuShown: true,
  431. },
  432. })
  433. cy.mount(
  434. <EditorProviders scope={scope}>
  435. <EditorLeftMenu />
  436. </EditorProviders>
  437. )
  438. cy.get<HTMLOptionElement>('#settings-menu-autoComplete option').then(
  439. options => {
  440. const values = [...options].map(o => o.value)
  441. expect(values).to.deep.eq(['true', 'false'])
  442. const texts = [...options].map(o => o.text)
  443. expect(texts).to.deep.eq(['On', 'Off'])
  444. }
  445. )
  446. })
  447. it('shows auto-close brackets menu correctly', function () {
  448. const scope = mockScope({
  449. ui: {
  450. leftMenuShown: true,
  451. },
  452. })
  453. cy.mount(
  454. <EditorProviders scope={scope}>
  455. <EditorLeftMenu />
  456. </EditorProviders>
  457. )
  458. cy.get<HTMLOptionElement>(
  459. '#settings-menu-autoPairDelimiters option'
  460. ).then(options => {
  461. const values = [...options].map(o => o.value)
  462. expect(values).to.deep.eq(['true', 'false'])
  463. const texts = [...options].map(o => o.text)
  464. expect(texts).to.deep.eq(['On', 'Off'])
  465. })
  466. })
  467. it('shows code check menu correctly', function () {
  468. const scope = mockScope({
  469. ui: {
  470. leftMenuShown: true,
  471. },
  472. })
  473. cy.mount(
  474. <EditorProviders scope={scope}>
  475. <EditorLeftMenu />
  476. </EditorProviders>
  477. )
  478. cy.get<HTMLOptionElement>(
  479. '#settings-menu-syntaxValidation option'
  480. ).then(options => {
  481. const values = [...options].map(o => o.value)
  482. expect(values).to.deep.eq(['true', 'false'])
  483. const texts = [...options].map(o => o.text)
  484. expect(texts).to.deep.eq(['On', 'Off'])
  485. })
  486. })
  487. it('shows editor theme menu correctly', function () {
  488. const editorThemes = ['editortheme-1', 'editortheme-2', 'editortheme-3']
  489. const legacyEditorThemes = [
  490. 'legacytheme-1',
  491. 'legacytheme-2',
  492. 'legacytheme-3',
  493. ]
  494. window.metaAttributesCache.set('ol-editorThemes', editorThemes)
  495. window.metaAttributesCache.set(
  496. 'ol-legacyEditorThemes',
  497. legacyEditorThemes
  498. )
  499. const scope = mockScope({
  500. ui: {
  501. leftMenuShown: true,
  502. },
  503. })
  504. cy.mount(
  505. <EditorProviders scope={scope}>
  506. <EditorLeftMenu />
  507. </EditorProviders>
  508. )
  509. cy.get<HTMLOptionElement>('#settings-menu-editorTheme option').then(
  510. options => {
  511. const values = [...options].map(o => o.value)
  512. expect(values).to.deep.eq([
  513. 'editortheme-1',
  514. 'editortheme-2',
  515. 'editortheme-3',
  516. '-',
  517. 'legacytheme-1',
  518. 'legacytheme-2',
  519. 'legacytheme-3',
  520. ])
  521. const texts = [...options].map(o => o.text)
  522. expect(texts).to.deep.eq([
  523. 'editortheme-1',
  524. 'editortheme-2',
  525. 'editortheme-3',
  526. '—————————————————',
  527. 'legacytheme-1 (Legacy)',
  528. 'legacytheme-2 (Legacy)',
  529. 'legacytheme-3 (Legacy)',
  530. ])
  531. }
  532. )
  533. })
  534. it('shows overall theme menu correctly', function () {
  535. const scope = mockScope({
  536. ui: {
  537. leftMenuShown: true,
  538. },
  539. })
  540. cy.mount(
  541. <EditorProviders scope={scope}>
  542. <EditorLeftMenu />
  543. </EditorProviders>
  544. )
  545. cy.get<HTMLOptionElement>('#settings-menu-overallTheme option').then(
  546. options => {
  547. const values = [...options].map(o => o.value)
  548. expect(values).to.deep.eq(['', 'light-'])
  549. const texts = [...options].map(o => o.text)
  550. expect(texts).to.deep.eq(['Overall Theme 1', 'Overall Theme 2'])
  551. }
  552. )
  553. })
  554. it('shows keybindings menu correctly', function () {
  555. const scope = mockScope({
  556. ui: {
  557. leftMenuShown: true,
  558. },
  559. })
  560. cy.mount(
  561. <EditorProviders scope={scope}>
  562. <EditorLeftMenu />
  563. </EditorProviders>
  564. )
  565. cy.get<HTMLOptionElement>('#settings-menu-mode option').then(
  566. options => {
  567. const values = [...options].map(o => o.value)
  568. expect(values).to.deep.eq(['default', 'vim', 'emacs'])
  569. const texts = [...options].map(o => o.text)
  570. expect(texts).to.deep.eq(['None', 'Vim', 'Emacs'])
  571. }
  572. )
  573. })
  574. it('shows font size menu correctly', function () {
  575. const scope = mockScope({
  576. ui: {
  577. leftMenuShown: true,
  578. },
  579. })
  580. cy.mount(
  581. <EditorProviders scope={scope}>
  582. <EditorLeftMenu />
  583. </EditorProviders>
  584. )
  585. cy.get<HTMLOptionElement>('#settings-menu-fontSize option').then(
  586. options => {
  587. const values = [...options].map(o => o.value)
  588. expect(values).to.deep.eq([
  589. '10',
  590. '11',
  591. '12',
  592. '13',
  593. '14',
  594. '16',
  595. '18',
  596. '20',
  597. '22',
  598. '24',
  599. ])
  600. const texts = [...options].map(o => o.text)
  601. expect(texts).to.deep.eq([
  602. '10px',
  603. '11px',
  604. '12px',
  605. '13px',
  606. '14px',
  607. '16px',
  608. '18px',
  609. '20px',
  610. '22px',
  611. '24px',
  612. ])
  613. }
  614. )
  615. })
  616. it('shows font family menu correctly', function () {
  617. const scope = mockScope({
  618. ui: {
  619. leftMenuShown: true,
  620. },
  621. })
  622. cy.mount(
  623. <EditorProviders scope={scope}>
  624. <EditorLeftMenu />
  625. </EditorProviders>
  626. )
  627. cy.get<HTMLOptionElement>('#settings-menu-fontFamily option').then(
  628. options => {
  629. const values = [...options].map(o => o.value)
  630. expect(values).to.deep.eq(['monaco', 'lucida', 'opendyslexicmono'])
  631. const texts = [...options].map(o => o.text)
  632. expect(texts).to.deep.eq([
  633. 'Monaco / Menlo / Consolas',
  634. 'Lucida / Source Code Pro',
  635. 'OpenDyslexic Mono',
  636. ])
  637. }
  638. )
  639. })
  640. it('shows line height menu correctly', function () {
  641. const scope = mockScope({
  642. ui: {
  643. leftMenuShown: true,
  644. },
  645. })
  646. cy.mount(
  647. <EditorProviders scope={scope}>
  648. <EditorLeftMenu />
  649. </EditorProviders>
  650. )
  651. cy.get<HTMLOptionElement>('#settings-menu-lineHeight option').then(
  652. options => {
  653. const values = [...options].map(o => o.value)
  654. expect(values).to.deep.eq(['compact', 'normal', 'wide'])
  655. const texts = [...options].map(o => o.text)
  656. expect(texts).to.deep.eq(['Compact', 'Normal', 'Wide'])
  657. }
  658. )
  659. })
  660. it('shows pdf viewer menu correctly', function () {
  661. const scope = mockScope({
  662. ui: {
  663. leftMenuShown: true,
  664. },
  665. })
  666. cy.mount(
  667. <EditorProviders scope={scope}>
  668. <EditorLeftMenu />
  669. </EditorProviders>
  670. )
  671. cy.get<HTMLOptionElement>('#settings-menu-pdfViewer option').then(
  672. options => {
  673. const values = [...options].map(o => o.value)
  674. expect(values).to.deep.eq(['pdfjs', 'native'])
  675. const texts = [...options].map(o => o.text)
  676. expect(texts).to.deep.eq(['Overleaf', 'Browser'])
  677. }
  678. )
  679. })
  680. })
  681. describe('help menu', function () {
  682. it('shows hotkeys modal correctly', function () {
  683. const scope = mockScope({
  684. ui: {
  685. leftMenuShown: true,
  686. },
  687. })
  688. cy.mount(
  689. <EditorProviders scope={scope}>
  690. <EditorLeftMenu />
  691. </EditorProviders>
  692. )
  693. cy.findByRole('button', { name: 'Show Hotkeys' }).click()
  694. cy.findByText('Hotkeys')
  695. })
  696. it('shows correct url for documentation', function () {
  697. const scope = mockScope({
  698. ui: {
  699. leftMenuShown: true,
  700. },
  701. })
  702. cy.mount(
  703. <EditorProviders scope={scope}>
  704. <EditorLeftMenu />
  705. </EditorProviders>
  706. )
  707. cy.findByRole('link', { name: 'Documentation' }).should(
  708. 'have.attr',
  709. 'href',
  710. '/learn'
  711. )
  712. })
  713. it('shows correct contact us modal', function () {
  714. const scope = mockScope({
  715. ui: {
  716. leftMenuShown: true,
  717. },
  718. })
  719. cy.mount(
  720. <EditorProviders scope={scope}>
  721. <EditorLeftMenu />
  722. </EditorProviders>
  723. )
  724. cy.findByRole('button', { name: 'Contact Us' }).click()
  725. cy.findByText('Affected project URL (Optional)')
  726. })
  727. })
  728. })
  729. describe('for anonymous users', function () {
  730. it('render minimal menu', function () {
  731. const scope = mockScope({
  732. ui: {
  733. leftMenuShown: true,
  734. },
  735. })
  736. window.metaAttributesCache.set('ol-anonymous', true)
  737. Object.assign(getMeta('ol-ExposedSettings'), { ieeeBrandId: 123 })
  738. cy.mount(
  739. <EditorProviders scope={scope}>
  740. <EditorLeftMenu />
  741. </EditorProviders>
  742. )
  743. // Download Menu
  744. cy.findByRole('heading', { name: 'Download' })
  745. cy.findByRole('link', { name: 'Source' })
  746. cy.findByRole('link', { name: 'PDF' })
  747. // Actions Menu
  748. cy.findByRole('heading', { name: 'Actions' }).should('not.exist')
  749. cy.findByRole('button', { name: 'Copy Project' }).should('not.exist')
  750. cy.findByRole('button', { name: 'Word Count' }).should('not.exist')
  751. // Sync Menu
  752. cy.findByRole('heading', { name: 'Sync' }).should('not.exist')
  753. cy.findByRole('button', { name: 'Dropbox' }).should('not.exist')
  754. cy.findByRole('button', { name: 'Git' }).should('not.exist')
  755. cy.findByRole('button', { name: 'GitHub' }).should('not.exist')
  756. // Settings Menu
  757. cy.findByRole('heading', { name: 'Settings' }).should('not.exist')
  758. cy.findByLabelText('Compiler').should('not.exist')
  759. cy.findByLabelText('TeX Live version').should('not.exist')
  760. cy.findByLabelText('Main document').should('not.exist')
  761. cy.findByLabelText('Spell check').should('not.exist')
  762. cy.findByLabelText('Auto-complete').should('not.exist')
  763. cy.findByLabelText('Auto-close brackets').should('not.exist')
  764. cy.findByLabelText('Code check').should('not.exist')
  765. cy.findByLabelText('Editor theme').should('not.exist')
  766. cy.findByLabelText('Overall theme').should('not.exist')
  767. cy.findByLabelText('Keybindings').should('not.exist')
  768. cy.findByLabelText('Font Size').should('not.exist')
  769. cy.findByLabelText('Font Family').should('not.exist')
  770. cy.findByLabelText('Line Height').should('not.exist')
  771. cy.findByLabelText('PDF Viewer').should('not.exist')
  772. // Help Menu
  773. cy.findByRole('heading', { name: 'Help' })
  774. cy.findByRole('button', { name: 'Show Hotkeys' })
  775. cy.findByRole('button', { name: 'Documentation' }).should('not.exist')
  776. cy.findByRole('link', { name: 'Contact Us' }).should('not.exist')
  777. })
  778. })
  779. })