Merge pull request #3330 from overleaf/jel-toolbar-btns

Hide toolbar text and show tooltip when out of space

GitOrigin-RevId: 5a73b69e7d92695c4f8691a747307908550e3790
This commit is contained in:
Jessica Lawshe 2020-11-09 08:52:22 -06:00 committed by Copybot
parent da8663fd0f
commit 21ffe27bdd
10 changed files with 593 additions and 172 deletions

View file

@ -1,12 +1,17 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Dropdown, MenuItem } from 'react-bootstrap'
import { Dropdown, MenuItem, OverlayTrigger, Tooltip } from 'react-bootstrap'
import { useTranslation, Trans } from 'react-i18next'
import Icon from '../../../shared/components/icon'
export const topFileTypes = ['bbl', 'gls', 'ind']
function PreviewDownloadButton({ isCompiling, outputFiles, pdfDownloadUrl }) {
function PreviewDownloadButton({
isCompiling,
outputFiles,
pdfDownloadUrl,
showText
}) {
let topFiles = []
let otherFiles = []
const { t } = useTranslation()
@ -26,16 +31,46 @@ function PreviewDownloadButton({ isCompiling, outputFiles, pdfDownloadUrl }) {
})
}
let textStyle = {}
if (!showText) {
textStyle = {
position: 'absolute',
right: '-100vw'
}
}
const buttonElement = (
<a
className="btn btn-xs btn-info"
disabled={isCompiling || !pdfDownloadUrl}
download
href={pdfDownloadUrl || '#'}
>
<Icon type="download" modifier="fw" />
<span className="toolbar-text" style={textStyle}>
{t('download_pdf')}
</span>
</a>
)
return (
<Dropdown id="download-dropdown" disabled={isCompiling}>
<a
className="btn btn-xs btn-info"
disabled={isCompiling || !pdfDownloadUrl}
download
href={pdfDownloadUrl || '#'}
>
<Icon type="download" modifier="fw" /> {t('download_pdf')}
</a>
<Dropdown
id="download-dropdown"
className="toolbar-item"
disabled={isCompiling}
>
{showText ? (
buttonElement
) : (
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id="tooltip-download-pdf">{t('download_pdf')}</Tooltip>
}
>
{buttonElement}
</OverlayTrigger>
)}
<Dropdown.Toggle
className="btn btn-xs btn-info dropdown-toggle"
aria-label={t('toggle_output_files_list')}
@ -79,7 +114,8 @@ function FileList({ listType, list }) {
PreviewDownloadButton.propTypes = {
isCompiling: PropTypes.bool.isRequired,
outputFiles: PropTypes.array,
pdfDownloadUrl: PropTypes.string
pdfDownloadUrl: PropTypes.string,
showText: PropTypes.bool.isRequired
}
FileList.propTypes = {

View file

@ -1,4 +1,5 @@
import React from 'react'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import { useTranslation } from 'react-i18next'
@ -7,77 +8,122 @@ import Icon from '../../../shared/components/icon'
function PreviewLogsToggleButton({
onToggle,
showLogs,
logsState: { nErrors, nWarnings }
logsState: { nErrors, nWarnings },
showText
}) {
const toggleButtonClasses = classNames('btn', 'btn-xs', 'btn-toggle-logs', {
'btn-danger': !showLogs && nErrors,
'btn-warning': !showLogs && !nErrors && nWarnings,
'btn-default': showLogs || (!nErrors && !nWarnings)
})
const { t } = useTranslation()
const toggleButtonClasses = classNames(
'btn',
'btn-xs',
'btn-toggle-logs',
'toolbar-item',
{
'btn-danger': !showLogs && nErrors,
'btn-warning': !showLogs && !nErrors && nWarnings,
'btn-default': showLogs || (!nErrors && !nWarnings)
}
)
let textStyle = {}
if (!showText) {
textStyle = {
position: 'absolute',
right: '-100vw'
}
}
function handleOnClick(e) {
e.currentTarget.blur()
onToggle()
}
return (
const buttonElement = (
<button
id="logs-toggle"
type="button"
className={toggleButtonClasses}
onClick={handleOnClick}
>
{showLogs ? (
<ViewPdfButton />
<ViewPdfButton textStyle={textStyle} />
) : (
<CompilationResultIndicator nErrors={nErrors} nWarnings={nWarnings} />
<CompilationResultIndicator
textStyle={textStyle}
nErrors={nErrors}
nWarnings={nWarnings}
/>
)}
</button>
)
return showText ? (
buttonElement
) : (
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id="tooltip-logs-toggle">
{showLogs ? t('view_pdf') : t('view_logs')}
</Tooltip>
}
>
{buttonElement}
</OverlayTrigger>
)
}
function CompilationResultIndicator({ nErrors, nWarnings }) {
function CompilationResultIndicator({ textStyle, nErrors, nWarnings }) {
if (nErrors || nWarnings) {
return (
<LogsCompilationResultIndicator
logType={nErrors ? 'errors' : 'warnings'}
nLogs={nErrors || nWarnings}
textStyle={textStyle}
/>
)
} else {
return <ViewLogsButton />
return <ViewLogsButton textStyle={textStyle} />
}
}
function LogsCompilationResultIndicator({ logType, nLogs }) {
function LogsCompilationResultIndicator({ textStyle, logType, nLogs }) {
const { t } = useTranslation()
const label =
logType === 'errors' ? t('your_project_has_errors') : t('view_warnings')
return (
<>
<Icon type="file-text-o" />
<span className="btn-toggle-logs-label" aria-label={label}>
<span
className="btn-toggle-logs-label toolbar-text"
aria-label={label}
style={textStyle}
>
{`${label} (${nLogs > 9 ? '9+' : nLogs})`}
</span>
</>
)
}
function ViewLogsButton() {
function ViewLogsButton({ textStyle }) {
const { t } = useTranslation()
return (
<>
<Icon type="file-text-o" />
<span className="btn-toggle-logs-label">{t('view_logs')}</span>
<span className="toolbar-text" style={textStyle}>
{t('view_logs')}
</span>
</>
)
}
function ViewPdfButton() {
function ViewPdfButton({ textStyle }) {
const { t } = useTranslation()
return (
<>
<Icon type="file-pdf-o" />
<span className="btn-toggle-logs-label">{t('view_pdf')}</span>
<span className="toolbar-text" style={textStyle}>
{t('view_pdf')}
</span>
</>
)
}
@ -89,12 +135,22 @@ PreviewLogsToggleButton.propTypes = {
nWarnings: PropTypes.number.isRequired,
nLogEntries: PropTypes.number.isRequired
}),
showLogs: PropTypes.bool.isRequired
showLogs: PropTypes.bool.isRequired,
showText: PropTypes.bool.isRequired
}
LogsCompilationResultIndicator.propTypes = {
logType: PropTypes.string.isRequired,
nLogs: PropTypes.number.isRequired
nLogs: PropTypes.number.isRequired,
textStyle: PropTypes.object.isRequired
}
ViewLogsButton.propTypes = {
textStyle: PropTypes.object.isRequired
}
ViewPdfButton.propTypes = {
textStyle: PropTypes.object.isRequired
}
export default PreviewLogsToggleButton

View file

@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Dropdown, MenuItem } from 'react-bootstrap'
import { Dropdown, MenuItem, OverlayTrigger, Tooltip } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import Icon from '../../../shared/components/icon'
@ -17,7 +17,8 @@ function PreviewRecompileButton({
onRunSyntaxCheckNow,
onSetAutoCompile,
onSetDraftMode,
onSetSyntaxCheck
onSetSyntaxCheck,
showText
}) {
const { t } = useTranslation()
@ -55,18 +56,43 @@ function PreviewRecompileButton({
onSetSyntaxCheck(false)
}
return (
<Dropdown id="pdf-recompile-dropdown" className="btn-recompile-group">
let compilingProps = {}
let recompileProps = {}
function _hideText(keepAria) {
return {
'aria-hidden': !keepAria,
style: {
position: 'absolute',
right: '-100vw'
}
}
}
if (!showText) {
compilingProps = _hideText(isCompiling || isClearingCache)
recompileProps = _hideText(!isCompiling || !isClearingCache)
} else if (isCompiling || isClearingCache) {
recompileProps = _hideText()
} else {
compilingProps = _hideText()
}
const buttonElement = (
<Dropdown
id="pdf-recompile-dropdown"
className="btn-recompile-group toolbar-item"
>
<button className="btn btn-recompile" onClick={onRecompile}>
<Icon type="refresh" spin={isCompiling} />
{isCompiling || isClearingCache ? (
<span className="btn-recompile-label">
{t('compiling')}
&hellip;
</span>
) : (
<span className="btn-recompile-label">{t('recompile')}</span>
)}
<span id="text-compiling" className="toolbar-text" {...compilingProps}>
{t('compiling')}
&hellip;
</span>
<span id="text-recompile" className="toolbar-text" {...recompileProps}>
{t('recompile')}
</span>
</button>
<Dropdown.Toggle
aria-label={t('toggle_compile_options_menu')}
@ -115,6 +141,21 @@ function PreviewRecompileButton({
</Dropdown.Menu>
</Dropdown>
)
return showText ? (
buttonElement
) : (
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id="tooltip-download-pdf">
{isCompiling || isClearingCache ? t('compiling') : t('recompile')}
</Tooltip>
}
>
{buttonElement}
</OverlayTrigger>
)
}
PreviewRecompileButton.propTypes = {
@ -131,7 +172,8 @@ PreviewRecompileButton.propTypes = {
onRunSyntaxCheckNow: PropTypes.func.isRequired,
onSetAutoCompile: PropTypes.func.isRequired,
onSetDraftMode: PropTypes.func.isRequired,
onSetSyntaxCheck: PropTypes.func.isRequired
onSetSyntaxCheck: PropTypes.func.isRequired,
showText: PropTypes.bool.isRequired
}
export default PreviewRecompileButton

View file

@ -1,8 +1,14 @@
import React from 'react'
import React, { useRef, useState } from 'react'
import PropTypes from 'prop-types'
import PreviewDownloadButton from './preview-download-button'
import PreviewRecompileButton from './preview-recompile-button'
import PreviewLogsToggleButton from './preview-logs-toggle-button'
import useResizeObserver from '../../../shared/hooks/use-resize-observer'
function _getElementWidth(element) {
if (!element) return 0
return Math.ceil(element.getBoundingClientRect().width)
}
function PreviewToolbar({
compilerState,
@ -18,8 +24,173 @@ function PreviewToolbar({
pdfDownloadUrl,
showLogs
}) {
const showTextRef = useRef(true)
const showToggleTextRef = useRef(true)
const toolbarRef = useRef()
const recompileWidthDifferenceRef = useRef()
const recompileLongerTextRef = useRef()
const [showText, setShowText] = useState(showTextRef.current)
const [showToggleText, setShowToggleText] = useState(
showToggleTextRef.current
)
function checkCanShowText(observedElement) {
// toolbar items can be in 3 states:
// all text, only toggle logs w/text and icons on others, all icons
// states depend on available space in the toolbar
const toolbarWidth =
observedElement &&
observedElement.contentRect &&
observedElement.contentRect.width
if (!toolbarWidth) return
_checkRecompileStateWidths()
let textWidths = 0
let itemsWidth = _getItemsWidth() // could be with or without text
// get widths of text only
// required for _checkToggleText, even if currently showing text
const textElements = toolbarRef.current.querySelectorAll('.toolbar-text')
textElements.forEach(item => {
if (item.getAttribute('aria-hidden') !== 'true') {
textWidths += _getElementWidth(item)
}
})
const logsToggleText = toolbarRef.current.querySelector(
'#logs-toggle .toolbar-text'
)
const logsToggleTextWidth = _getElementWidth(logsToggleText)
if (!showTextRef.current && !showToggleTextRef.current) {
// itemsWidth was calculated without any text shown
itemsWidth += textWidths
} else if (!showTextRef.current && showToggleTextRef.current) {
// itemsWidth was calculated with toggle text but no other text
// only add text width for other items and then
// subtract toggle text width, since it is already in itemsWidth
itemsWidth += parseInt(textWidths - logsToggleTextWidth, 10)
}
// only add extra if recompile button is in state with smaller length
if (
recompileWidthDifferenceRef.current &&
recompileLongerTextRef.current &&
((!compilerState.isCompiling &&
recompileLongerTextRef.current === 'compiling') ||
(compilerState.isCompiling &&
recompileLongerTextRef.current === 'recompile'))
) {
itemsWidth += recompileWidthDifferenceRef.current
}
itemsWidth += 10 // add extra for some spacing between items
let canShowText = itemsWidth < toolbarWidth
if (!canShowText) {
_checkToggleText(
toolbarWidth,
logsToggleTextWidth,
itemsWidth,
textWidths
)
} else if (showToggleTextRef.current !== true) {
setShowToggleText(true)
showToggleTextRef.current = true
}
setShowText(canShowText)
showTextRef.current = canShowText
}
function _checkRecompileStateWidths() {
// check recompile/compiling button text.
// Do not want to hide and then show text when recompiling
if (
recompileWidthDifferenceRef.current ||
recompileWidthDifferenceRef.current === 0
)
return
const textCompiling = toolbarRef.current.querySelector('#text-compiling')
const textRecompile = toolbarRef.current.querySelector('#text-recompile')
const textCompilingWidth = _getElementWidth(textCompiling)
const textRecompileWidth = _getElementWidth(textRecompile)
const textLengthDifference = Math.abs(
parseInt(textCompilingWidth - textRecompileWidth, 10)
)
recompileWidthDifferenceRef.current = textLengthDifference
// ignore if equal
if (textRecompileWidth > textCompilingWidth) {
recompileLongerTextRef.current = 'recompile'
} else if (textRecompileWidth < textCompilingWidth) {
recompileLongerTextRef.current = 'compiling'
}
}
function _checkToggleText(
toolbarWidth,
logsToggleTextWidth,
itemsWithTextWidth,
textWidths
) {
// check to see if we can still show the toggle button text
let toggleWithTextWidth = 0
let toggleWithoutTextWidth = 0
const itemsWithoutTextWidth = parseInt(itemsWithTextWidth - textWidths, 10)
const logsToggle = toolbarRef.current.querySelector('#logs-toggle')
const logsToggleWidth = _getElementWidth(logsToggle)
// logsToggleWidth could be with or without text
if (showToggleTextRef.current) {
toggleWithTextWidth = logsToggleWidth
toggleWithoutTextWidth = parseInt(
logsToggleWidth - logsToggleTextWidth,
10
)
} else {
toggleWithTextWidth = parseInt(logsToggleWidth + logsToggleTextWidth, 10)
toggleWithoutTextWidth = logsToggleWidth
}
const itemsWithoutTextAndToggleWidth = parseInt(
itemsWithoutTextWidth - toggleWithoutTextWidth,
10
)
const itemsWithIconsExceptToggleWidth = parseInt(
itemsWithoutTextAndToggleWidth + toggleWithTextWidth,
10
)
const canShowToggleText = itemsWithIconsExceptToggleWidth < toolbarWidth
if (canShowToggleText !== showToggleTextRef.current) {
setShowToggleText(canShowToggleText)
showToggleTextRef.current = canShowToggleText
}
}
function _getItemsWidth() {
const toolbarItems = toolbarRef.current.querySelectorAll('.toolbar-item')
let itemWidth = 0
toolbarItems.forEach(item => {
itemWidth += _getElementWidth(item)
})
return itemWidth
}
useResizeObserver(toolbarRef, logsState, checkCanShowText)
return (
<div className="toolbar toolbar-pdf">
<div
className="toolbar toolbar-pdf"
id="toolbar-preview"
data-testid="toolbar-preview"
ref={toolbarRef}
>
<div className="toolbar-pdf-left">
<PreviewRecompileButton
compilerState={compilerState}
@ -29,11 +200,13 @@ function PreviewToolbar({
onSetDraftMode={onSetDraftMode}
onSetSyntaxCheck={onSetSyntaxCheck}
onClearCache={onClearCache}
showText={showText}
/>
<PreviewDownloadButton
isCompiling={compilerState.isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
showText={showText}
/>
</div>
<div className="toolbar-pdf-right">
@ -41,6 +214,7 @@ function PreviewToolbar({
logsState={logsState}
showLogs={showLogs}
onToggle={onToggleLogs}
showText={showToggleText}
/>
</div>
</div>

View file

@ -0,0 +1,38 @@
import { useLayoutEffect, useRef } from 'react'
function useResizeObserver(observedElement, observedData, callback) {
const resizeObserver = useRef()
function observe() {
resizeObserver.current = new ResizeObserver(function(elementsObserved) {
callback(elementsObserved[0])
})
}
function unobserve(observedCurrent) {
resizeObserver.current.unobserve(observedCurrent)
}
useLayoutEffect(
() => {
if ('ResizeObserver' in window) {
const observedCurrent = observedElement && observedElement.current
if (observedCurrent) {
observe(observedElement.current)
}
if (resizeObserver.current && observedCurrent) {
resizeObserver.current.observe(observedCurrent)
}
return () => {
unobserve(observedCurrent)
}
}
},
[observedElement, observedData]
)
}
export default useResizeObserver

View file

@ -103,6 +103,10 @@
margin-left: @line-height-computed / 4;
}
.toolbar-text {
padding-left: @padding-xs;
}
.pdf-viewer {
iframe {
width: 100%;

View file

@ -19,17 +19,36 @@ describe('<PreviewDownloadButton />', function() {
}
}
it('should disable the button and dropdown toggle when compiling', function() {
const isCompiling = true
const outputFiles = undefined
function renderPreviewDownloadButton(
isCompiling,
outputFiles,
pdfDownloadUrl,
showText
) {
if (isCompiling === undefined) isCompiling = false
if (showText === undefined) showText = true
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={undefined}
outputFiles={outputFiles || []}
pdfDownloadUrl={pdfDownloadUrl}
showText={showText}
/>
)
expect(screen.getByText('Download PDF').getAttribute('disabled')).to.exist
}
it('should disable the button and dropdown toggle when compiling', function() {
const isCompiling = true
const outputFiles = undefined
renderPreviewDownloadButton(isCompiling, outputFiles)
expect(
screen
.getByText('Download PDF')
.closest('a')
.getAttribute('disabled')
).to.exist
const buttons = screen.getAllByRole('button')
expect(buttons.length).to.equal(1) // the dropdown toggle
expect(buttons[0].getAttribute('disabled')).to.exist
@ -40,41 +59,35 @@ describe('<PreviewDownloadButton />', function() {
it('should disable the PDF button when there is no PDF', function() {
const isCompiling = false
const outputFiles = []
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={undefined}
/>
)
expect(screen.getByText('Download PDF').getAttribute('disabled')).to.exist
renderPreviewDownloadButton(isCompiling, outputFiles)
expect(
screen
.getByText('Download PDF')
.closest('a')
.getAttribute('disabled')
).to.exist
})
it('should enable the PDF button when there is a main PDF', function() {
const isCompiling = false
const outputFiles = []
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
expect(screen.getByText('Download PDF').getAttribute('href')).to.equal(
pdfDownloadUrl
)
expect(screen.getByText('Download PDF').getAttribute('disabled')).to.not
.exist
renderPreviewDownloadButton(isCompiling, outputFiles, pdfDownloadUrl)
expect(
screen
.getByText('Download PDF')
.closest('a')
.getAttribute('href')
).to.equal(pdfDownloadUrl)
expect(
screen
.getByText('Download PDF')
.closest('a')
.getAttribute('disabled')
).to.not.exist
})
it('should enable the dropdown when not compiling', function() {
const isCompiling = false
const outputFiles = []
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
renderPreviewDownloadButton(isCompiling, outputFiles, pdfDownloadUrl)
const buttons = screen.getAllByRole('button')
expect(buttons[0]).to.exist
expect(buttons[0].getAttribute('disabled')).to.not.exist
@ -93,13 +106,7 @@ describe('<PreviewDownloadButton />', function() {
makeFile('output.blg')
]
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
renderPreviewDownloadButton(isCompiling, outputFiles, pdfDownloadUrl)
const menuItems = screen.getAllByRole('menuitem')
expect(menuItems.length).to.equal(outputFiles.length - 1) // main PDF is listed separately
@ -138,13 +145,9 @@ describe('<PreviewDownloadButton />', function() {
const pdfFile = makeFile('output.pdf', true)
const bblFile = makeFile('output.bbl')
const outputFiles = [Object.assign({}, { ...bblFile }), bblFile, pdfFile]
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
renderPreviewDownloadButton(isCompiling, outputFiles, pdfDownloadUrl)
const bblMenuItems = screen.getAllByText((content, element) => {
return (
content !== '' && element.textContent === 'Download output.bbl file'
@ -157,16 +160,23 @@ describe('<PreviewDownloadButton />', function() {
const pdfFile = makeFile('output.pdf', true)
const pdfAltFile = makeFile('alt.pdf')
const outputFiles = [pdfFile, pdfAltFile]
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
renderPreviewDownloadButton(isCompiling, outputFiles, pdfDownloadUrl)
screen.getAllByRole('menuitem', { name: 'Download alt.pdf file' })
})
it('should show the button text when prop showText=true', function() {
const isCompiling = false
const showText = true
renderPreviewDownloadButton(isCompiling, [], pdfDownloadUrl, showText)
expect(screen.getByText('Download PDF').getAttribute('style')).to.be.null
})
it('should not show the button text when prop showText=false', function() {
const isCompiling = false
const showText = false
renderPreviewDownloadButton(isCompiling, [], pdfDownloadUrl, showText)
expect(screen.getByText('Download PDF').getAttribute('style')).to.equal(
'position: absolute; right: -100vw;'
)
})
describe('list divider and header', function() {
it('should display when there are top files and other files', function() {
const outputFiles = [
@ -176,13 +186,7 @@ describe('<PreviewDownloadButton />', function() {
makeFile('output.log')
]
render(
<PreviewDownloadButton
isCompiling={false}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
renderPreviewDownloadButton(false, outputFiles, pdfDownloadUrl, true)
screen.getByText('Other output files')
screen.getByRole('separator')
@ -194,13 +198,7 @@ describe('<PreviewDownloadButton />', function() {
makeFile('output.gls')
]
render(
<PreviewDownloadButton
isCompiling={false}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
renderPreviewDownloadButton(false, outputFiles, pdfDownloadUrl, true)
expect(screen.queryByText('Other output files')).to.not.exist
expect(screen.queryByRole('separator')).to.not.exist
@ -208,13 +206,7 @@ describe('<PreviewDownloadButton />', function() {
it('should not display when there are other files and no top files', function() {
const outputFiles = [makeFile('output.log')]
render(
<PreviewDownloadButton
isCompiling={false}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
renderPreviewDownloadButton(false, outputFiles, pdfDownloadUrl, true)
expect(screen.queryByText('Other output files')).to.not.exist
expect(screen.queryByRole('separator')).to.not.exist

View file

@ -1,9 +1,27 @@
import React from 'react'
import { expect } from 'chai'
import { screen, render } from '@testing-library/react'
import PreviewLogsToggleButton from '../../../../../frontend/js/features/preview/components/preview-logs-toggle-button'
describe('<PreviewLogsToggleButton />', function() {
function renderPreviewLogsToggleButton(
logsState,
onToggleLogs,
showLogs,
showText
) {
if (showText === undefined) showText = true
render(
<PreviewLogsToggleButton
logsState={logsState}
onToggle={onToggleLogs}
showLogs={showLogs}
showText={showText}
/>
)
}
describe('basic toggle functionality', function() {
const logsState = {
nErrors: 0,
@ -13,24 +31,12 @@ describe('<PreviewLogsToggleButton />', function() {
const onToggleLogs = () => {}
it('should render a view logs button when previewing the PDF', function() {
const showLogs = false
render(
<PreviewLogsToggleButton
logsState={logsState}
showLogs={showLogs}
onToggle={onToggleLogs}
/>
)
renderPreviewLogsToggleButton(logsState, onToggleLogs, showLogs)
screen.getByText('View logs')
})
it('should render a view PDF button when viewing logs', function() {
const showLogs = true
render(
<PreviewLogsToggleButton
logsState={logsState}
showLogs={showLogs}
onToggle={onToggleLogs}
/>
)
renderPreviewLogsToggleButton(logsState, onToggleLogs, showLogs)
screen.getByText('View PDF')
})
})
@ -43,13 +49,7 @@ describe('<PreviewLogsToggleButton />', function() {
nWarnings: 0,
nLogEntries: 0
}
render(
<PreviewLogsToggleButton
logsState={logsState}
showLogs={showLogs}
onToggle={onToggleLogs}
/>
)
renderPreviewLogsToggleButton(logsState, onToggleLogs, showLogs)
screen.getByText('View logs')
})
@ -59,13 +59,7 @@ describe('<PreviewLogsToggleButton />', function() {
nWarnings: 0,
nLogEntries: 0
}
render(
<PreviewLogsToggleButton
logsState={logsState}
showLogs={showLogs}
onToggle={onToggleLogs}
/>
)
renderPreviewLogsToggleButton(logsState, onToggleLogs, showLogs)
screen.getByText(`Your project has errors (${logsState.nErrors})`)
})
@ -75,13 +69,7 @@ describe('<PreviewLogsToggleButton />', function() {
nWarnings: 1,
nLogEntries: 0
}
render(
<PreviewLogsToggleButton
logsState={logsState}
showLogs={showLogs}
onToggle={onToggleLogs}
/>
)
renderPreviewLogsToggleButton(logsState, onToggleLogs, showLogs)
screen.getByText(`Your project has errors (${logsState.nErrors})`)
})
@ -91,13 +79,7 @@ describe('<PreviewLogsToggleButton />', function() {
nWarnings: 1,
nLogEntries: 0
}
render(
<PreviewLogsToggleButton
logsState={logsState}
showLogs={showLogs}
onToggle={onToggleLogs}
/>
)
renderPreviewLogsToggleButton(logsState, onToggleLogs, showLogs)
screen.getByText(`View warnings (${logsState.nWarnings})`)
})
@ -107,14 +89,30 @@ describe('<PreviewLogsToggleButton />', function() {
nWarnings: 0,
nLogEntries: 0
}
render(
<PreviewLogsToggleButton
logsState={logsState}
showLogs={showLogs}
onToggle={onToggleLogs}
/>
)
renderPreviewLogsToggleButton(logsState, onToggleLogs, showLogs)
screen.getByText('Your project has errors (9+)')
})
it('should show the button text when prop showText=true', function() {
const logsState = {
nErrors: 0,
nWarnings: 0,
nLogEntries: 0
}
const showText = true
renderPreviewLogsToggleButton(logsState, onToggleLogs, showLogs, showText)
expect(screen.getByText('View logs').getAttribute('style')).to.be.null
})
it('should not show the button text when prop showText=false', function() {
const logsState = {
nErrors: 0,
nWarnings: 0,
nLogEntries: 0
}
const showText = false
renderPreviewLogsToggleButton(logsState, onToggleLogs, showLogs, showText)
expect(screen.getByText('View logs').getAttribute('style')).to.equal(
'position: absolute; right: -100vw;'
)
})
})
})

View file

@ -95,10 +95,24 @@ describe('<PreviewRecompileButton />', function() {
})
})
function renderPreviewRecompileButton(compilerState = {}) {
it('should show the button text when prop showText=true', function() {
const showText = true
renderPreviewRecompileButton({}, showText)
expect(screen.getByText('Recompile').getAttribute('style')).to.be.null
})
it('should not show the button text when prop showText=false', function() {
const showText = false
renderPreviewRecompileButton({}, showText)
expect(screen.getByText('Recompile').getAttribute('style')).to.equal(
'position: absolute; right: -100vw;'
)
})
function renderPreviewRecompileButton(compilerState = {}, showText) {
if (!compilerState.logEntries) {
compilerState.logEntries = {}
}
if (showText === undefined) showText = true
render(
<PreviewRecompileButton
compilerState={{
@ -115,6 +129,7 @@ describe('<PreviewRecompileButton />', function() {
onSetDraftMode={() => {}}
onSetSyntaxCheck={() => {}}
onClearCache={onClearCache}
showText={showText}
/>
)
}

View file

@ -0,0 +1,66 @@
import React from 'react'
import sinon from 'sinon'
import { expect } from 'chai'
import { screen, render } from '@testing-library/react'
import PreviewToolbar from '../../../../../frontend/js/features/preview/components/preview-toolbar'
describe('<PreviewToolbar />', function() {
const onClearCache = sinon.stub()
const onRecompile = sinon.stub()
const onRunSyntaxCheckNow = sinon.stub()
const onSetAutoCompile = sinon.stub()
const onSetDraftMode = sinon.stub()
const onSetSyntaxCheck = sinon.stub()
const onToggleLogs = sinon.stub()
function renderPreviewToolbar(compilerState = {}, logState = {}, showLogs) {
render(
<PreviewToolbar
compilerState={{
isAutoCompileOn: true,
isClearingCache: false,
isCompiling: false,
isDraftModeOn: false,
isSyntaxCheckOn: false,
logEntries: {},
...compilerState
}}
logsState={{ nErrors: 0, nWarnings: 0, nLogEntries: 0, ...logState }}
onClearCache={onClearCache}
onRecompile={onRecompile}
onRunSyntaxCheckNow={onRunSyntaxCheckNow}
onSetAutoCompile={onSetAutoCompile}
onSetDraftMode={onSetDraftMode}
onSetSyntaxCheck={onSetSyntaxCheck}
onToggleLogs={onToggleLogs}
outputFiles={[]}
pdfDownloadUrl="/download-pdf-url"
showLogs={showLogs || false}
/>
)
}
it('renders the toolbar', function() {
renderPreviewToolbar()
screen.getByText('Recompile')
screen.getByText('Download PDF')
screen.getByText('View logs')
})
it('all toolbar items have "toolbar-item" class and text has "toolbar-text"', function() {
renderPreviewToolbar()
const toolbar = screen.getByTestId('toolbar-preview')
for (const toolbarSection of toolbar.children) {
for (const toolbarItem of toolbarSection.children) {
expect(toolbarItem.className).to.contain('toolbar-item')
for (const parts of toolbarItem.children) {
for (const part of parts.children) {
if (part.nodeName !== 'LI' && part.textContent) {
expect(part.className).to.contain('toolbar-text')
}
}
}
}
}
})
})