diff --git a/cypress/integration/history.spec.ts b/cypress/integration/history.spec.ts index 4bc485288..8dc00cc0e 100644 --- a/cypress/integration/history.spec.ts +++ b/cypress/integration/history.spec.ts @@ -5,25 +5,84 @@ */ describe('History', () => { - beforeEach(() => { - cy.visit('/history') - }) describe('History Mode', () => { + beforeEach(() => { + cy.visit('/history') + }) + it('Cards', () => { - cy.get('div.card') - .should('be.visible') + cy.get('div.card').should('be.visible') }) it('Table', () => { - cy.get('i.fa-table') - .click() - cy.get('table.history-table') - .should('be.visible') + cy.get('[data-cypress-id="history-mode-table"]').click() + cy.get('[data-cypress-id="history-table"]').should('be.visible') + }) + }) + + describe('entry title', () => { + describe('is as given when not empty', () => { + beforeEach(() => { + cy.clearLocalStorage('history') + cy.intercept('GET', '/mock-backend/api/private/me/history', { + body: [ + { + identifier: 'cypress', + title: 'Features', + lastVisited: '2020-05-16T22:26:56.547Z', + pinStatus: false, + tags: [] + } + ] + }) + cy.visit('/history') + }) + + it('in table view', () => { + cy.get('[data-cypress-id="history-mode-table"]').click() + cy.get('[data-cypress-id="history-table"]').should('be.visible') + cy.get('[data-cypress-id="history-entry-title"]').contains('Features') + }) + + it('in cards view', () => { + cy.get('[data-cypress-id="history-entry-title"]').contains('Features') + }) + }) + describe('is untitled when not empty', () => { + beforeEach(() => { + cy.clearLocalStorage('history') + cy.intercept('GET', '/mock-backend/api/private/me/history', { + body: [ + { + identifier: 'cypress-no-title', + title: '', + lastVisited: '2020-05-16T22:26:56.547Z', + pinStatus: false, + tags: [] + } + ] + }) + cy.visit('/history') + }) + + it('in table view', () => { + cy.get('[data-cypress-id="history-mode-table"]').click() + cy.get('[data-cypress-id="history-table"]').should('be.visible') + cy.get('[data-cypress-id="history-entry-title"]').contains('Untitled') + }) + + it('in cards view', () => { + cy.get('[data-cypress-id="history-entry-title"]').contains('Untitled') + }) }) }) describe('Pinning', () => { + beforeEach(() => { + cy.visit('/history') + }) + describe('working', () => { beforeEach(() => { cy.intercept('PUT', '/mock-backend/api/private/me/history/features', (req) => { @@ -32,29 +91,17 @@ describe('History', () => { }) it('Cards', () => { - cy.get('div.card') - .should('be.visible') - cy.get('.history-pin.btn') - .first() - .as('pin-button') - cy.get('@pin-button') - .should('have.class', 'pinned') - .click() - cy.get('@pin-button') - .should('not.have.class', 'pinned') + cy.get('div.card').should('be.visible') + cy.get('.history-pin.btn').first().as('pin-button') + cy.get('@pin-button').should('have.class', 'pinned').click() + cy.get('@pin-button').should('not.have.class', 'pinned') }) it('Table', () => { - cy.get('i.fa-table') - .click() - cy.get('.history-pin.btn') - .first() - .as('pin-button') - cy.get('@pin-button') - .should('have.class', 'pinned') - .click() - cy.get('@pin-button') - .should('not.have.class', 'pinned') + cy.get('i.fa-table').click() + cy.get('.history-pin.btn').first().as('pin-button') + cy.get('@pin-button').should('have.class', 'pinned').click() + cy.get('@pin-button').should('not.have.class', 'pinned') }) }) @@ -66,23 +113,15 @@ describe('History', () => { }) it('Cards', () => { - cy.get('div.card') - .should('be.visible') - cy.get('.fa-thumb-tack') - .first() - .click() - cy.get('.notifications-area .toast') - .should('be.visible') + cy.get('div.card').should('be.visible') + cy.get('.fa-thumb-tack').first().click() + cy.get('.notifications-area .toast').should('be.visible') }) it('Table', () => { - cy.get('i.fa-table') - .click() - cy.get('.fa-thumb-tack') - .first() - .click() - cy.get('.notifications-area .toast') - .should('be.visible') + cy.get('i.fa-table').click() + cy.get('.fa-thumb-tack').first().click() + cy.get('.notifications-area .toast').should('be.visible') }) }) }) diff --git a/public/mock-backend/api/private/me/history b/public/mock-backend/api/private/me/history index 1a7327dae..f5d49d61a 100644 --- a/public/mock-backend/api/private/me/history +++ b/public/mock-backend/api/private/me/history @@ -1,12 +1,12 @@ [ { "identifier": "29QLD0AmT-adevdOPECtqg", - "title": "HedgeDoc community call 2020-04-26", + "title": "", "lastVisited": "2020-05-16T22:26:56.547Z", "pinStatus": false, "tags": [ - "HedgeDoc", - "Community Call" + "empty title", + "should be untitled" ] }, { diff --git a/src/components/editor-page/document-bar/document-info/document-info-line-word-count.tsx b/src/components/editor-page/document-bar/document-info/document-info-line-word-count.tsx index 53162857b..381f03458 100644 --- a/src/components/editor-page/document-bar/document-info/document-info-line-word-count.tsx +++ b/src/components/editor-page/document-bar/document-info/document-info-line-word-count.tsx @@ -14,6 +14,7 @@ import type { OnWordCountCalculatedMessage } from '../../../render-page/window-p import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message' import { useEditorReceiveHandler } from '../../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler' import { useEffectOnRendererReady } from '../../../render-page/window-post-message-communicator/hooks/use-effect-on-renderer-ready' +import { cypressId } from '../../../../utils/cypress-attribute' /** * Creates a new info line for the document information dialog that holds the @@ -42,7 +43,7 @@ export const DocumentInfoLineWordCount: React.FC = () => { - + diff --git a/src/components/history-page/history-card/history-card.tsx b/src/components/history-page/history-card/history-card.tsx index e44902b62..bcd46f3ef 100644 --- a/src/components/history-page/history-card/history-card.tsx +++ b/src/components/history-page/history-card/history-card.tsx @@ -14,6 +14,8 @@ import type { HistoryEntryProps, HistoryEventHandlers } from '../history-content import { PinButton } from '../pin-button/pin-button' import { formatHistoryDate } from '../utils' import './history-card.scss' +import { useHistoryEntryTitle } from '../use-history-entry-title' +import { cypressId } from '../../../utils/cypress-attribute' export const HistoryCard: React.FC = ({ entry, @@ -29,6 +31,8 @@ export const HistoryCard: React.FC = ( onDeleteClick(entry.identifier) }, [onDeleteClick, entry.identifier]) + const entryTitle = useHistoryEntryTitle(entry) + return (
@@ -38,7 +42,9 @@ export const HistoryCard: React.FC = (
- {entry.title} + + {entryTitle} +
{DateTime.fromISO(entry.lastVisited).toRelative()} diff --git a/src/components/history-page/history-table/history-table-row.tsx b/src/components/history-page/history-table/history-table-row.tsx index 152862a08..83281ae68 100644 --- a/src/components/history-page/history-table/history-table-row.tsx +++ b/src/components/history-page/history-table/history-table-row.tsx @@ -11,6 +11,8 @@ import { EntryMenu } from '../entry-menu/entry-menu' import type { HistoryEntryProps, HistoryEventHandlers } from '../history-content/history-content' import { PinButton } from '../pin-button/pin-button' import { formatHistoryDate } from '../utils' +import { useHistoryEntryTitle } from '../use-history-entry-title' +import { cypressId } from '../../../utils/cypress-attribute' export const HistoryTableRow: React.FC = ({ entry, @@ -18,11 +20,12 @@ export const HistoryTableRow: React.FC onRemoveClick, onDeleteClick }) => { + const entryTitle = useHistoryEntryTitle(entry) return ( - - {entry.title} + + {entryTitle} {formatHistoryDate(entry.lastVisited)} @@ -42,7 +45,7 @@ export const HistoryTableRow: React.FC /> onRemoveClick(entry.identifier)} diff --git a/src/components/history-page/history-table/history-table.tsx b/src/components/history-page/history-table/history-table.tsx index 5655bbe28..bae0409bb 100644 --- a/src/components/history-page/history-table/history-table.tsx +++ b/src/components/history-page/history-table/history-table.tsx @@ -11,6 +11,7 @@ import { Pager } from '../../common/pagination/pager' import type { HistoryEntriesProps, HistoryEventHandlers } from '../history-content/history-content' import { HistoryTableRow } from './history-table-row' import './history-table.scss' +import { cypressId } from '../../../utils/cypress-attribute' export const HistoryTable: React.FC = ({ entries, @@ -22,7 +23,7 @@ export const HistoryTable: React.FC }) => { useTranslation() return ( - +
diff --git a/src/components/history-page/history-toolbar/history-toolbar.tsx b/src/components/history-page/history-toolbar/history-toolbar.tsx index abfc287df..45b594b35 100644 --- a/src/components/history-page/history-toolbar/history-toolbar.tsx +++ b/src/components/history-page/history-toolbar/history-toolbar.tsx @@ -22,6 +22,7 @@ import { HistoryEntryOrigin } from '../../../redux/history/types' import { importHistoryEntries, refreshHistoryState, setHistoryEntries } from '../../../redux/history/methods' import { showErrorNotification } from '../../../redux/ui-notifications/methods' import { useApplicationState } from '../../../hooks/common/use-application-state' +import { cypressId } from '../../../utils/cypress-attribute' export type HistoryToolbarChange = (newState: HistoryToolbarState) => void @@ -223,7 +224,11 @@ export const HistoryToolbar: React.FC = ({ onSettingsChange - + diff --git a/src/components/history-page/use-history-entry-title.ts b/src/components/history-page/use-history-entry-title.ts new file mode 100644 index 000000000..f7918b9dc --- /dev/null +++ b/src/components/history-page/use-history-entry-title.ts @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { HistoryEntry } from '../../redux/history/types' +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' + +/** + * Hook that returns the title of a note in the history if present or the translation for "untitled" otherwise. + * @param entry The history entry containing a title property, that might be an empty string. + * @return A memoized string containing either the title of the entry or the translated version of "untitled". + */ +export const useHistoryEntryTitle = (entry: HistoryEntry): string => { + const { t } = useTranslation() + return useMemo(() => { + return entry.title !== '' ? entry.title : t('editor.untitledNote') + }, [t, entry]) +}