| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- import PdfSynctexControls from '../../../../frontend/js/features/pdf-preview/components/pdf-synctex-controls'
- import { cloneDeep } from 'lodash'
- import { useDetachCompileContext as useCompileContext } from '../../../../frontend/js/shared/context/detach-compile-context'
- import { useFileTreeData } from '../../../../frontend/js/shared/context/file-tree-data-context'
- import { useEffect } from 'react'
- import { EditorProviders } from '../../helpers/editor-providers'
- import { mockScope } from './scope'
- import { detachChannel, testDetachChannel } from '../../helpers/detach-channel'
- import { FindResult } from '@/features/file-tree/util/path'
- const mockHighlights = [
- {
- page: 1,
- h: 85.03936,
- v: 509.999878,
- width: 441.921265,
- height: 8.855677,
- },
- {
- page: 1,
- h: 85.03936,
- v: 486.089539,
- width: 441.921265,
- height: 8.855677,
- },
- ]
- type Position = {
- page: number
- offset: { top: number; left: number }
- pageSize: { height: number; width: number }
- }
- const mockPosition: Position = {
- page: 1,
- offset: { top: 10, left: 10 },
- pageSize: { height: 500, width: 500 },
- }
- const mockSelectedEntities = [{ type: 'doc' }] as FindResult[]
- const WithPosition = ({ mockPosition }: { mockPosition: Position }) => {
- const { setPosition } = useCompileContext()
- // mock PDF scroll position update
- useEffect(() => {
- setPosition(mockPosition)
- }, [mockPosition, setPosition])
- return null
- }
- // mock PDF scroll position update
- const setDetachedPosition = (mockPosition: Position) => {
- testDetachChannel.postMessage({
- role: 'detacher',
- event: 'state-position',
- data: { value: mockPosition },
- })
- }
- const WithSelectedEntities = ({
- mockSelectedEntities = [],
- }: {
- mockSelectedEntities: FindResult[]
- }) => {
- const { setSelectedEntities } = useFileTreeData()
- useEffect(() => {
- setSelectedEntities(mockSelectedEntities)
- }, [mockSelectedEntities, setSelectedEntities])
- return null
- }
- describe('<PdfSynctexControls/>', function () {
- beforeEach(function () {
- window.metaAttributesCache.set('ol-project_id', 'test-project')
- window.metaAttributesCache.set('ol-preventCompileOnLoad', false)
- cy.interceptEvents()
- })
- it('handles clicks on sync buttons', function () {
- cy.interceptCompile()
- const scope = mockScope()
- cy.mount(
- <EditorProviders scope={scope}>
- <WithPosition mockPosition={mockPosition} />
- <WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
- <PdfSynctexControls />
- </EditorProviders>
- )
- cy.waitForCompile()
- cy.get('.synctex-control-icon').should('have.length', 2)
- // mock editor cursor position update
- cy.window().then(win => {
- win.dispatchEvent(
- new CustomEvent('cursor:editor:update', {
- detail: { row: 100, column: 10 },
- })
- )
- })
- cy.wrap(null).then(() => {
- setDetachedPosition(mockPosition)
- })
- cy.interceptAsync({ pathname: '/project/*/sync/code' }, 'sync-code').then(
- syncCodeResponse => {
- cy.findByRole('button', { name: 'Go to code location in PDF' }).click()
- cy.findByRole('button', { name: 'Go to code location in PDF' })
- .should('be.disabled')
- .then(() => {
- syncCodeResponse.resolve({
- body: { pdf: cloneDeep(mockHighlights) },
- })
- })
- cy.wait('@sync-code')
- }
- )
- cy.interceptAsync({ pathname: '/project/*/sync/pdf' }, 'sync-pdf').then(
- syncPdfResponse => {
- cy.findByRole('button', { name: /^Go to PDF location in code/ }).click()
- cy.findByRole('button', { name: /^Go to PDF location in code/ })
- .should('be.disabled')
- .then(() => {
- syncPdfResponse.resolve({
- body: { code: [{ file: 'main.tex', line: 100 }] },
- })
- })
- cy.wait('@sync-pdf')
- }
- )
- })
- it('disables button when multiple entities are selected', function () {
- cy.interceptCompile()
- const scope = mockScope()
- cy.mount(
- <EditorProviders scope={scope}>
- <WithPosition mockPosition={mockPosition} />
- <WithSelectedEntities
- mockSelectedEntities={
- [{ type: 'doc' }, { type: 'doc' }] as FindResult[]
- }
- />
- <PdfSynctexControls />
- </EditorProviders>
- )
- cy.waitForCompile()
- cy.findByRole('button', { name: 'Go to code location in PDF' }).should(
- 'be.disabled'
- )
- })
- it('disables button when a file is selected', function () {
- cy.interceptCompile()
- const scope = mockScope()
- cy.mount(
- <EditorProviders scope={scope}>
- <WithPosition mockPosition={mockPosition} />
- <WithSelectedEntities
- mockSelectedEntities={[{ type: 'fileRef' }] as FindResult[]}
- />
- <PdfSynctexControls />
- </EditorProviders>
- )
- cy.waitForCompile()
- cy.findByRole('button', { name: 'Go to code location in PDF' }).should(
- 'be.disabled'
- )
- })
- describe('with detacher role', function () {
- beforeEach(function () {
- window.metaAttributesCache.set('ol-detachRole', 'detacher')
- })
- it('does not have go to PDF location button nor arrow icon', function () {
- cy.interceptCompile()
- const scope = mockScope()
- cy.mount(
- <EditorProviders scope={scope}>
- <WithPosition mockPosition={mockPosition} />
- <WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
- <PdfSynctexControls />
- </EditorProviders>
- )
- cy.waitForCompile()
- cy.findByRole('button', { name: /^Go to PDF location in code/ }).should(
- 'not.exist'
- )
- cy.get('.synctex-control-icon').should('not.exist')
- })
- it('send set highlights action', function () {
- cy.interceptCompile()
- const scope = mockScope()
- cy.mount(
- <EditorProviders scope={scope}>
- <WithPosition mockPosition={mockPosition} />
- <WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
- <PdfSynctexControls />
- </EditorProviders>
- )
- cy.waitForCompile()
- // mock editor cursor position update
- cy.window().then(win => {
- win.dispatchEvent(
- new CustomEvent('cursor:editor:update', {
- detail: { row: 100, column: 10 },
- })
- )
- })
- cy.spy(detachChannel, 'postMessage').as('postDetachMessage')
- cy.interceptAsync({ pathname: '/project/*/sync/code' }, 'sync-code').then(
- syncCodeResponse => {
- cy.findByRole('button', {
- name: 'Go to code location in PDF',
- })
- .should('not.be.disabled')
- .click()
- cy.findByRole('button', {
- name: 'Go to code location in PDF',
- })
- .should('be.disabled')
- .then(() => {
- syncCodeResponse.resolve({
- body: { pdf: cloneDeep(mockHighlights) },
- })
- })
- cy.wait('@sync-code')
- }
- )
- cy.findByRole('button', {
- name: 'Go to code location in PDF',
- }).should('not.be.disabled')
- // synctex is called locally and the result are broadcast for the detached tab
- // NOTE: can't use `.to.deep.include({…})` as it doesn't match the nested array
- cy.get('@postDetachMessage').should('have.been.calledWith', {
- role: 'detacher',
- event: 'action-setHighlights',
- data: { args: [mockHighlights] },
- })
- })
- it('reacts to sync to code action', function () {
- cy.interceptCompile()
- const scope = mockScope()
- cy.mount(
- <EditorProviders scope={scope}>
- <WithPosition mockPosition={mockPosition} />
- <WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
- <PdfSynctexControls />
- </EditorProviders>
- )
- cy.waitForCompile()
- cy.interceptAsync({ pathname: '/project/*/sync/pdf' }, 'sync-pdf')
- .then(syncPdfResponse => {
- syncPdfResponse.resolve({
- body: { code: [{ file: 'main.tex', line: 100 }] },
- })
- testDetachChannel.postMessage({
- role: 'detached',
- event: 'action-sync-to-code',
- data: {
- args: [mockPosition],
- },
- })
- })
- .wait('@sync-pdf')
- })
- })
- describe('with detached role', function () {
- beforeEach(function () {
- window.metaAttributesCache.set('ol-detachRole', 'detached')
- })
- it('does not have go to code location button nor arrow icon', function () {
- cy.interceptCompile()
- const scope = mockScope()
- cy.mount(
- <EditorProviders scope={scope}>
- <WithPosition mockPosition={mockPosition} />
- <PdfSynctexControls />
- </EditorProviders>
- )
- cy.waitForCompile()
- cy.findByRole('button', {
- name: 'Go to code location in PDF',
- }).should('not.exist')
- cy.get('.synctex-control-icon').should('not.exist')
- })
- it('send go to code line action', function () {
- cy.interceptCompile()
- const scope = mockScope()
- cy.mount(
- <EditorProviders scope={scope}>
- <PdfSynctexControls />
- </EditorProviders>
- )
- cy.waitForCompile().then(() => {
- testDetachChannel.postMessage({
- role: 'detacher',
- event: `state-position`,
- data: { value: mockPosition },
- })
- })
- cy.findByRole('button', {
- name: /^Go to PDF location in code/,
- })
- cy.findByRole('button', { name: /^Go to PDF location in code/ }).should(
- 'not.be.disabled'
- )
- cy.spy(detachChannel, 'postMessage').as('postDetachMessage')
- cy.findByRole('button', { name: /^Go to PDF location in code/ }).click()
- // the button is only disabled when the state is updated
- cy.findByRole('button', { name: /^Go to PDF location in code/ }).should(
- 'not.be.disabled'
- )
- cy.get('.synctex-spin-icon').should('not.exist')
- cy.get('@postDetachMessage').should('have.been.calledWith', {
- role: 'detached',
- event: 'action-sync-to-code',
- data: {
- args: [mockPosition, 72],
- },
- })
- })
- it('update inflight state', function () {
- cy.interceptCompile()
- const scope = mockScope()
- cy.mount(
- <EditorProviders scope={scope}>
- <WithPosition mockPosition={mockPosition} />
- <PdfSynctexControls />
- </EditorProviders>
- )
- cy.waitForCompile().then(() => {
- testDetachChannel.postMessage({
- role: 'detacher',
- event: `state-position`,
- data: { value: mockPosition },
- })
- })
- cy.findByRole('button', { name: /^Go to PDF location in code/ }).should(
- 'not.be.disabled'
- )
- cy.get('.synctex-spin-icon').should('not.exist')
- cy.wrap(null).then(() => {
- testDetachChannel.postMessage({
- role: 'detacher',
- event: 'state-sync-to-code-inflight',
- data: { value: true },
- })
- })
- cy.findByRole('button', { name: /^Go to PDF location in code/ }).should(
- 'be.disabled'
- )
- cy.get('.synctex-spin-icon').should('have.length', 1)
- cy.wrap(null).then(() => {
- testDetachChannel.postMessage({
- role: 'detacher',
- event: 'state-sync-to-code-inflight',
- data: { value: false },
- })
- })
- cy.findByRole('button', { name: /^Go to PDF location in code/ }).should(
- 'not.be.disabled'
- )
- cy.get('.synctex-spin-icon').should('not.exist')
- })
- })
- })
|