templates.spec.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import { isExcludedBySharding, startWith } from './helpers/config'
  2. import { ensureUserExists, login } from './helpers/login'
  3. import { createProject, NEW_PROJECT_BUTTON_MATCHER } from './helpers/project'
  4. const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com'
  5. const ADMIN_USER = 'admin@example.com'
  6. const REGULAR_USER = 'user@example.com'
  7. const TEMPLATES_USER = 'templates@example.com'
  8. // Re-use value for "exists" and "does not exist" tests
  9. const LABEL_BROWSE_TEMPLATES = 'Browse templates'
  10. describe('Templates', () => {
  11. ensureUserExists({ email: TEMPLATES_USER })
  12. ensureUserExists({ email: WITHOUT_PROJECTS_USER })
  13. let OVERLEAF_TEMPLATES_USER_ID: string
  14. before(function () {
  15. login(TEMPLATES_USER)
  16. cy.visit('/')
  17. cy.get('meta[name="ol-user_id"]').then(el => {
  18. OVERLEAF_TEMPLATES_USER_ID = el.attr('content')!
  19. })
  20. })
  21. function varsFn() {
  22. return {
  23. OVERLEAF_TEMPLATES_USER_ID,
  24. OVERLEAF_NEW_PROJECT_TEMPLATE_LINKS:
  25. '[{"name":"All Templates","url":"/templates/all"}]',
  26. }
  27. }
  28. describe('enabled in Server Pro', () => {
  29. if (isExcludedBySharding('PRO_CUSTOM_2')) return
  30. startWith({
  31. pro: true,
  32. varsFn,
  33. })
  34. ensureUserExists({ email: REGULAR_USER })
  35. ensureUserExists({ email: ADMIN_USER, isAdmin: true })
  36. it('should show templates link on welcome page', () => {
  37. login(WITHOUT_PROJECTS_USER)
  38. cy.visit('/')
  39. cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES })
  40. .should('have.attr', 'href', '/templates')
  41. .click()
  42. cy.url().should('match', /\/templates$/)
  43. })
  44. it('should have templates feature', () => {
  45. login(TEMPLATES_USER)
  46. const name = `Template ${Date.now()}`
  47. const description = `Template Description ${Date.now()}`
  48. cy.visit('/')
  49. createProject(name, { type: 'Example project' }).as('templateProjectId')
  50. cy.findByRole('navigation', {
  51. name: 'Project actions',
  52. })
  53. .findByRole('button', { name: 'Menu' })
  54. .click()
  55. cy.findByText('Manage Template').click()
  56. cy.findByText('Template Description')
  57. .click()
  58. .parent()
  59. .get('textarea')
  60. .type(description)
  61. cy.findByText('Publish').click()
  62. cy.findByText('Publishing…').parent().should('be.disabled')
  63. cy.findByText('Publish').should('not.exist')
  64. cy.findByText('Unpublish', { timeout: 60_000 })
  65. cy.findByText('Republish')
  66. cy.findByText('View it in the template gallery').click()
  67. cy.url()
  68. .should('match', /\/templates\/[a-f0-9]{24}$/)
  69. .as('templateURL')
  70. cy.findAllByText(name).first().should('exist')
  71. cy.findByText(description)
  72. cy.findByText('Open as Template')
  73. cy.findByText('Unpublish')
  74. cy.findByText('Republish')
  75. cy.get('img')
  76. .should('have.attr', 'src')
  77. .and('match', /\/v\/0\//)
  78. cy.findByText('Republish').click()
  79. cy.findByText('Publishing…').parent().should('be.disabled')
  80. cy.findByText('Republish', { timeout: 60_000 })
  81. cy.get('img', { timeout: 60_000 })
  82. .should('have.attr', 'src')
  83. .and('match', /\/v\/1\//)
  84. // custom tag
  85. const tagName = `${Date.now()}`
  86. cy.visit('/')
  87. cy.findByText(name)
  88. .parent()
  89. .parent()
  90. .within(() => cy.get('input[type="checkbox"]').first().check())
  91. cy.get('.project-list-sidebar-scroll').within(() => {
  92. cy.findAllByText('New tag').first().click()
  93. })
  94. cy.focused().type(tagName)
  95. cy.findByText('Create').click()
  96. cy.get('.project-list-sidebar-scroll').within(() => {
  97. cy.findByText(tagName)
  98. .parent()
  99. .within(() => cy.get('.name').should('have.text', `${tagName} (1)`))
  100. })
  101. // Check listing
  102. cy.visit('/templates')
  103. cy.findByText(tagName)
  104. cy.visit('/templates/all')
  105. cy.findByText(name)
  106. cy.visit(`/templates/${tagName}`)
  107. cy.findByText(name)
  108. // Unpublish via template page
  109. cy.get('@templateURL').then(url => cy.visit(`${url}`))
  110. cy.findByText('Unpublish').click()
  111. cy.url().should('match', /\/templates$/)
  112. cy.get('@templateURL').then(url =>
  113. cy.visit(`${url}`, {
  114. failOnStatusCode: false,
  115. })
  116. )
  117. cy.findByText('Not found')
  118. cy.visit('/templates/all')
  119. cy.findByText(name).should('not.exist')
  120. cy.visit(`/templates/${tagName}`)
  121. cy.findByText(name).should('not.exist')
  122. // Publish again
  123. cy.get('@templateProjectId').then(projectId =>
  124. cy.visit(`/project/${projectId}`)
  125. )
  126. cy.findByRole('navigation', {
  127. name: 'Project actions',
  128. })
  129. .findByRole('button', { name: 'Menu' })
  130. .click()
  131. cy.findByText('Manage Template').click()
  132. cy.findByText('Publish').click()
  133. cy.findByText('Unpublish', { timeout: 60_000 })
  134. // Should assign a new template id
  135. cy.findByText('View it in the template gallery').click()
  136. cy.url()
  137. .should('match', /\/templates\/[a-f0-9]{24}$/)
  138. .as('newTemplateURL')
  139. cy.get('@newTemplateURL').then(newURL => {
  140. cy.get('@templateURL').then(prevURL => {
  141. expect(newURL).to.match(/\/templates\/[a-f0-9]{24}$/)
  142. expect(prevURL).to.not.equal(newURL)
  143. })
  144. })
  145. // Open project from template
  146. login(REGULAR_USER)
  147. cy.visit('/templates')
  148. cy.findByText(tagName).click()
  149. cy.findByText(name).click()
  150. cy.findByText('Open as Template').click()
  151. cy.url().should('match', /\/project\/[a-f0-9]{24}$/)
  152. cy.get('.project-name').should('contain.text', 'Your Paper') // might have (1) suffix
  153. cy.findByRole('navigation', {
  154. name: 'Project actions',
  155. })
  156. .findByRole('button', { name: 'Menu' })
  157. .click()
  158. cy.findByText('Word Count') // wait for lazy loading
  159. cy.findByText('Manage Template').should('not.exist')
  160. // Check management as regular user
  161. cy.get('@newTemplateURL').then(url => cy.visit(`${url}`))
  162. cy.findByText('Open as Template')
  163. cy.findByText('Unpublish').should('not.exist')
  164. cy.findByText('Republish').should('not.exist')
  165. // Check management as admin user
  166. login(ADMIN_USER)
  167. cy.get('@newTemplateURL').then(url => cy.visit(`${url}`))
  168. cy.findByText('Open as Template')
  169. cy.findByText('Unpublish')
  170. cy.findByText('Republish')
  171. cy.get('@templateProjectId').then(projectId =>
  172. cy.visit(`/project/${projectId}`)
  173. )
  174. cy.findByRole('navigation', {
  175. name: 'Project actions',
  176. })
  177. .findByRole('button', { name: 'Menu' })
  178. .click()
  179. cy.findByText('Manage Template').click()
  180. cy.findByText('Unpublish')
  181. // Back to templates user
  182. login(TEMPLATES_USER)
  183. // Unpublish via editor
  184. cy.get('@templateProjectId').then(projectId =>
  185. cy.visit(`/project/${projectId}`)
  186. )
  187. cy.findByRole('navigation', {
  188. name: 'Project actions',
  189. })
  190. .findByRole('button', { name: 'Menu' })
  191. .click()
  192. cy.findByText('Manage Template').click()
  193. cy.findByText('Unpublish').click()
  194. cy.findByText('Publish')
  195. cy.visit('/templates/all')
  196. cy.findByText(name).should('not.exist')
  197. // check for template links, after creating the first project
  198. cy.visit('/')
  199. cy.findAllByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }).click()
  200. cy.findAllByText('All Templates')
  201. .first()
  202. .parent()
  203. .should('have.attr', 'href', '/templates/all')
  204. })
  205. })
  206. function checkDisabled() {
  207. it('should not have templates feature', () => {
  208. login(TEMPLATES_USER)
  209. cy.visit('/')
  210. createProject('maybe templates')
  211. cy.findByRole('navigation', {
  212. name: 'Project actions',
  213. })
  214. .findByRole('button', { name: 'Menu' })
  215. .click()
  216. cy.findByText('Word Count') // wait for lazy loading
  217. cy.findByText('Manage Template').should('not.exist')
  218. cy.visit('/templates', { failOnStatusCode: false })
  219. cy.findByText('Not found')
  220. cy.visit('/templates/all', { failOnStatusCode: false })
  221. cy.findByText('Not found')
  222. // check for template links, after creating the first project
  223. cy.visit('/')
  224. cy.findAllByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }).click()
  225. cy.findAllByText('All Templates').should('not.exist')
  226. })
  227. it('should not show templates link on welcome page', () => {
  228. login(WITHOUT_PROJECTS_USER)
  229. cy.visit('/')
  230. cy.findByText(NEW_PROJECT_BUTTON_MATCHER) // wait for lazy loading
  231. cy.findByText(LABEL_BROWSE_TEMPLATES).should('not.exist')
  232. })
  233. }
  234. describe('disabled Server Pro', () => {
  235. if (isExcludedBySharding('PRO_DEFAULT_2')) return
  236. startWith({ pro: true })
  237. checkDisabled()
  238. })
  239. describe('unavailable in CE', () => {
  240. if (isExcludedBySharding('CE_CUSTOM_1')) return
  241. startWith({
  242. pro: false,
  243. varsFn,
  244. })
  245. checkDisabled()
  246. })
  247. })