src/components/OrgFile/OrgFile.integration.test.js
import React from 'react';
import thunk from 'redux-thunk';
import { MemoryRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import OrgFile from './';
import HeaderBar from '../HeaderBar';
import readFixture from '../../../test_helpers/index';
import rootReducer from '../../reducers/';
import { setPath, parseFile } from '../../actions/org';
import { setShouldLogIntoDrawer } from '../../actions/base';
import { Map, Set, fromJS, List } from 'immutable';
import { formatDistanceToNow } from 'date-fns';
import { render, fireEvent, cleanup } from '@testing-library/react';
// Debugging help:
// console.log(prettyDOM(container, 999999999999999999999999));
import '@testing-library/jest-dom/extend-expect';
import { STATIC_FILE_PREFIX } from '../../lib/org_utils';
afterEach(cleanup);
describe('Render all views', () => {
jest.mock('react-hotkeys', () => {
const React = require('react');
const Fragment = React.Fragment;
return {
HotKeys: ({ children }) => <Fragment>{children}</Fragment>,
};
});
const testOrgFile = readFixture('main_test_file');
let store;
beforeEach(() => {
// Set global variable which can also be read from application code
window.testRunner = true;
let capture = Map();
capture = capture.set('captureTemplates', []);
store = createStore(
rootReducer,
{
org: {
past: [],
present: Map({
files: Map(),
fileSettings: [],
search: Map({
searchFilter: '',
searchFilterExpr: [],
}),
bookmarks: Map({
search: List(),
'task-list': List(),
refile: List(),
}),
}),
future: [],
},
syncBackend: Map({
isAuthenticated: true,
}),
capture,
base: new fromJS({
customKeybindings: {},
shouldTapTodoToAdvance: true,
isLoading: Set(),
finderTab: 'Search',
agendaTimeframe: 'Week',
preferEditRawValues: false,
}),
},
applyMiddleware(thunk)
);
store.dispatch(parseFile(STATIC_FILE_PREFIX + 'fixtureTestFile.org', testOrgFile));
store.dispatch(setPath(STATIC_FILE_PREFIX + 'fixtureTestFile.org'));
});
describe('Org Functionality', () => {
let container,
getByText,
getAllByText,
getByTitle,
getByTestId,
queryByText,
queryAllByText,
getByPlaceholderText;
beforeEach(() => {
let res = render(
<MemoryRouter keyLength={0} initialEntries={['/file/dir1/dir2/fixtureTestFile.org']}>
<Provider store={store}>
<HeaderBar />
<OrgFile path={STATIC_FILE_PREFIX + 'fixtureTestFile.org'} />
</Provider>
</MemoryRouter>
);
container = res.container;
getByText = res.getByText;
getAllByText = res.getAllByText;
getByTitle = res.getByTitle;
getByTestId = res.getByTestId;
queryByText = res.queryByText;
queryAllByText = res.queryAllByText;
getByPlaceholderText = res.getByPlaceholderText;
});
describe('Works with Org files without headlines', () => {
test('Works with a completely empty files', () => {
store.dispatch(
parseFile(STATIC_FILE_PREFIX + 'fixtureTestFile.org', readFixture('empty_file'))
);
expect(queryByText('This file has no headlines')).toBeTruthy();
expect(queryAllByText('Yes, your file has content.').length).toEqual(0);
// Sanity check, ensure that not the regular test file is loaded.
expect(queryByText('Top level header')).toBeFalsy();
});
});
describe('Actions within an Org file', () => {
test('Can select a header in an org file', () => {
expect(container.querySelector("[data-testid='org-clock-in']")).toBeFalsy();
fireEvent.click(getByText('Top level header'));
expect(container.querySelector("[data-testid='org-clock-in']")).toBeTruthy();
});
// Org Mode has keywords as workflow states and can cycle through
// them: https://orgmode.org/manual/Workflow-states.html
// In organice, we can cycle through them by swiping or by clicking
// (if enabled). This test checks for the latter.
test('Can advance todo state for selected header in an org file', () => {
// In the very beginning, the TODO is hidden, because the file
// starts folded down to the top level
expect(queryByText('TODO')).toBeFalsy();
// Cycle the top header (which will make the next headers
// [including the TODO] visible
fireEvent.click(queryByText('Top level header'));
// In the beginning, the TODO is not DONE
expect(queryByText('TODO')).toBeTruthy();
expect(queryByText('DONE')).toBeFalsy();
// Toggle the TODO
fireEvent.click(queryByText('TODO'));
// Then the TODO is DONE
expect(queryByText('TODO')).toBeFalsy();
expect(queryByText('DONE')).toBeTruthy();
});
// Same behaviour has `S-C-RET` in Emacs Org mode.
test('Can create a new header with an inherited todoKeyword', () => {
fireEvent.click(queryByText('Top level header'));
// Click 'plus' on the first header which is _not_ a todoKeyword header
fireEvent.click(container.querySelectorAll("[data-testid='header-action-plus']")[0]);
// "edit title" view has buttons to choose TODO or DONE
let drawerElem = getByTestId('drawer');
expect(drawerElem).toHaveTextContent('DONE');
// switch to "edit full title", which has no such buttons
fireEvent.click(getByTitle('Edit title'));
drawerElem = getByTestId('drawer');
expect(drawerElem).not.toHaveTextContent('DONE');
expect(getByTestId('titleLineInput').value).toEqual('');
// switch back to "edit title"
// TODO: Find out why resetting "editRawValues" is broken here
// maybe the popup is not closed properly? how to simulate that?
// clicking the top of the screen with
// fireEvent.click(container.querySelector('.header-bar__title'));
// crashes the tests
fireEvent.click(getByTitle('Edit title'));
// Click 'plus' on the second header which _is_ a todoKeyword header
fireEvent.click(queryByText('A todo item with schedule and deadline'));
fireEvent.click(container.querySelectorAll("[data-testid='header-action-plus']")[1]);
expect(getByTestId('titleLineInput').value).toEqual('');
// switch to "edit full title"
fireEvent.click(getByTitle('Edit title'));
expect(getByTestId('titleLineInput').value).toEqual('TODO ');
});
test('Can clock in & out of an event', () => {
expect(queryByText('Clock In')).toBeFalsy();
expect(queryByText('Clock Out')).toBeFalsy();
fireEvent.click(getByText('Top level header'));
expect(container.querySelector("[data-testid='org-clock-in']")).toBeTruthy();
expect(container.querySelector("[data-testid='org-clock-out']")).toBeFalsy();
expect(queryByText(':LOGBOOK:...')).toBeFalsy();
fireEvent.click(container.querySelector("[data-testid='org-clock-in']"));
expect(container.querySelector("[data-testid='org-clock-in']")).toBeFalsy();
expect(container.querySelector("[data-testid='org-clock-out']")).toBeTruthy();
expect(queryByText(':LOGBOOK:...')).toBeTruthy();
fireEvent.click(container.querySelector("[data-testid='org-clock-out']"));
expect(container.querySelector("[data-testid='org-clock-in']")).toBeTruthy();
expect(container.querySelector("[data-testid='org-clock-out']")).toBeFalsy();
expect(queryByText(':LOGBOOK:...')).toBeTruthy();
fireEvent.click(getByText(':LOGBOOK:...'));
expect(queryByText('CLOCK:')).toBeTruthy();
expect(queryByText('=> 0:00')).toBeTruthy();
});
});
describe('List item manipulation', () => {
test('Can create a new list item of same type in the middle of an existing list', () => {
const header = queryByText('A header with plain list items');
fireEvent.click(header);
fireEvent.click(queryByText('Plain list item 1'));
fireEvent.click(container.querySelectorAll("[data-testid='list-item-action-plus']")[0]);
const input = getByTestId('list-item-edit');
fireEvent.change(input, { target: { value: 'Plain list item 2a' } });
// Close the modal by clicking on the outside
fireEvent.click(getByText('Top level header'));
// New list item has the same UX
fireEvent.click(queryByText('Plain list item 2a'));
expect(container.querySelectorAll("[data-testid='list-item-action-plus']")[0]).toBeTruthy();
// The new item is inserted in the middle of 'item 1' and 'item 2'
expect(
store
.getState()
.org.present.getIn(['files', STATIC_FILE_PREFIX + 'fixtureTestFile.org', 'headers'])
.get(12)
.get('rawDescription')
).toContain('- Plain list item 1\n- Plain list item 2a\n- Plain list item 2');
});
});
describe('Tracking TODO state changes', () => {
describe('Default settings', () => {
test('Does not track TODO state change for repeating todos', () => {
expect(queryByText(':LOGBOOK:...')).toBeFalsy();
expect(store.getState().base.toJS().shouldLogIntoDrawer).toBeFalsy();
fireEvent.click(getByText('Another top level header'));
fireEvent.click(getByText('A repeating todo'));
fireEvent.click(queryByText('TODO'));
fireEvent.click(getByText('A repeating todo'));
expect(queryByText(':LOGBOOK:...')).toBeFalsy();
});
});
describe('Feature enabled', () => {
test('Does track TODO state change for repeating todos', () => {
expect(store.getState().base.toJS().shouldLogIntoDrawer).toBeFalsy();
store.dispatch(setShouldLogIntoDrawer(true));
expect(store.getState().base.toJS().shouldLogIntoDrawer).toBeTruthy();
fireEvent.click(getByText('Another top level header'));
fireEvent.click(getByText('A repeating todo'));
expect(queryByText(':LOGBOOK:...')).toBeFalsy();
expect(queryByText('<2020-04-05 Sun +1d>')).toBeTruthy();
fireEvent.click(queryByText('TODO'));
fireEvent.click(getByText('A repeating todo'));
// After the TODO is toggled, it's still just TODO, because
// the state got tracked
expect(queryByText('DONE')).toBeFalsy();
// TODO has been scheduled one day into the future
expect(queryByText('<2020-04-05 Sun +1d>')).toBeFalsy();
expect(queryByText('<2020-04-06 Mon +1d>')).toBeTruthy();
expect(queryByText(':LOGBOOK:...')).toBeTruthy();
});
});
});
describe('Renders everything starting from an Org file', () => {
test('renders an Org file', () => {
// INFO: This snapshot is semantically correct, but it does
// not have color theme information. We're implementing the
// color themes with an API that mutates the DOM in place (see
// `color.js::loadTheme`. We cannot use this API in jest
// tests, because calling the API does not yield the same
// side-effect as in a browser. Hence, some colors in this
// snapshot are off, but that's ok. We do colorScheme testing
// by eye and not with automated tests.
expect(getAllByText(/\*/)).toHaveLength(8);
expect(container).toMatchSnapshot();
});
describe('Custom todo sequences', () => {
test('It recognizes custom todo sequences and their DONE state', () => {
fireEvent.click(getByText('Top level header'));
expect(queryByText('TODO').classList.contains('todo-keyword--done-state')).toBe(false);
fireEvent.click(queryByText('TODO'));
expect(queryByText('DONE').classList.contains('todo-keyword--done-state')).toBe(true);
expect(queryByText('FINISHED').classList.contains('todo-keyword--done-state')).toBe(true);
});
});
describe('Undo / Redo', () => {
test('On loading an Org file, both are disabled', () => {
expect(getByTitle('Undo').classList.contains('header-bar__actions__item--disabled')).toBe(
true
);
expect(getByTitle('Redo').classList.contains('header-bar__actions__item--disabled')).toBe(
true
);
});
test('Undo becomes available on "edit header"', () => {
fireEvent.click(queryByText('Top level header'));
fireEvent.click(queryByText('TODO'));
// Open the the title edit textarea
fireEvent.click(container.querySelector("[data-testid='edit-header-title']"));
// Close the title edit textarea
fireEvent.click(queryByText('DONE'));
// Undo should become available
expect(getByTitle('Undo').classList.contains('header-bar__actions__item--disabled')).toBe(
false
);
});
});
describe('Planning items', () => {
test('deletes planning items', () => {
fireEvent.click(queryByText('Top level header'));
fireEvent.click(queryByText('A todo item with schedule and deadline'));
fireEvent.click(queryByText('<2019-09-19 Thu>'));
expect(queryByText('SCHEDULED:')).toBeTruthy();
expect(queryByText('DEADLINE:')).toBeTruthy();
// First: Delete "Scheduled" time
// The <input type="date"> field exposes a 'x' button to
// delete the time. There's no direct API to trigger it.
// Hence, get the element, remove the time and fire the
// 'change' event manually.
let timePicker = getByTestId('timestamp-selector');
timePicker.value = null;
fireEvent.change(timePicker);
expect(queryByText('SCHEDULED:')).toBeFalsy();
expect(queryByText('DEADLINE:')).toBeTruthy();
// Second: Delete "Deadline" time
fireEvent.click(queryByText('<2018-10-05 Fri>'));
timePicker = getByTestId('timestamp-selector');
timePicker.value = null;
fireEvent.change(timePicker);
expect(queryByText('SCHEDULED:')).toBeFalsy();
expect(queryByText('DEADLINE:')).toBeFalsy();
});
});
/* global global */
describe('Sharing', () => {
let windowSpy;
beforeEach(() => {
windowSpy = jest.spyOn(global, 'open');
windowSpy.mockImplementation((x) => x);
});
afterEach(() => {
windowSpy.mockRestore();
});
test('sends the selected header and its body as an email', () => {
fireEvent.click(queryByText('Another top level header'));
fireEvent.click(getByTestId('share'));
expect(global.open).toBeCalledWith(
`mailto:?subject=${encodeURIComponent(
'Another top level header'
)}&body=${encodeURIComponent('\n\nSome description content\n')}`
);
});
});
describe('Search', () => {
test('renders Search for an Org file', () => {
expect(queryByText('Search')).toBeFalsy();
expect(queryByText('A todo item with schedule and deadline')).toBeFalsy();
fireEvent.click(getByTitle('Show Search / Task List'));
const drawerElem = getByTestId('drawer');
expect(drawerElem).toHaveTextContent('A todo item with schedule and deadline');
});
test('searches in all headers', () => {
fireEvent.click(getByTitle('Show Search / Task List'));
const drawerElem = getByTestId('drawer');
const input = getByPlaceholderText(
'e.g. -DONE doc|man :simple|easy :assignee:nobody|none'
);
// All kinds of headers are visible
expect(drawerElem).toHaveTextContent('A todo item with schedule and deadline');
expect(drawerElem).toHaveTextContent('A header with tags');
expect(drawerElem).toHaveTextContent('Another top level header');
// Filter down to headers with tag :tag1:
fireEvent.change(input, { target: { value: ':tag1' } });
expect(drawerElem).toHaveTextContent('A header with tags');
expect(drawerElem).not.toHaveTextContent('Another top level header');
});
test('searches in sub-headers when narrowed', () => {
// Click 'narrow' on the first header
fireEvent.click(queryByText('Top level header'));
fireEvent.click(container.querySelectorAll("[data-testid='header-action-narrow']")[0]);
fireEvent.click(getByTitle('Show Search / Task List'));
const drawerElem = getByTestId('drawer');
// Only sub-headers are visible
expect(drawerElem).not.toHaveTextContent('A header with tags');
expect(drawerElem).not.toHaveTextContent('Another top level header');
expect(drawerElem).toHaveTextContent('A nested header');
expect(drawerElem).toHaveTextContent('A todo item with schedule and deadline');
});
});
describe('Refile', () => {
test('removes selected header and subheader from search', () => {
fireEvent.click(queryByText('Top level header'));
fireEvent.click(getByTestId('org-refile'));
const drawerElem = getByTestId('drawer');
expect(drawerElem).not.toHaveTextContent('Top level header');
expect(drawerElem).toHaveTextContent('Another top level header');
});
});
describe('TaskList', () => {
test('renders TaskList for an Org file', () => {
expect(queryByText('Task list')).toBeFalsy();
expect(queryByText('A todo item with schedule and deadline')).toBeFalsy();
fireEvent.click(getByTitle('Show Search / Task List'));
fireEvent.click(getByText('Task List'));
const drawerElem = getByTestId('drawer');
expect(drawerElem).not.toHaveTextContent('Top level header');
expect(drawerElem).toHaveTextContent('A todo item with schedule and deadline');
});
// Order by state first and then by date. Ergo TODO is before
// DONE and yesterday is before today.
test('orders tasks for an Org file', () => {
fireEvent.click(getByTitle('Show Search / Task List'));
const drawerElem = getByTestId('drawer');
expect(drawerElem).toMatchSnapshot();
});
test('search in TaskList filters headers (by default only with todoKeywords)', () => {
fireEvent.click(getByTitle('Show Search / Task List'));
const drawerElem = getByTestId('drawer');
const input = getByPlaceholderText(
'e.g. -DONE doc|man :simple|easy :assignee:nobody|none'
);
fireEvent.change(input, { target: { value: 'a search with no results' } });
expect(drawerElem).not.toHaveTextContent('A todo item with schedule and deadline');
fireEvent.change(input, { target: { value: 'todo item' } });
expect(drawerElem).toHaveTextContent('A todo item with schedule and deadline');
});
// More rigorous testing of the search parser is here:
// headline_filter_parser.unit.test.js
test('search in TaskList filters headers (on demand without todoKeywords)', () => {
fireEvent.click(getByTitle('Show Search / Task List'));
fireEvent.click(getByText('Task List'));
const drawerElem = getByTestId('drawer');
getByPlaceholderText('e.g. -DONE doc|man :simple|easy :assignee:nobody|none');
// Is a regular header without TODO keyword
expect(drawerElem).not.toHaveTextContent('Another top level header');
// Is a header with TODO keyword
expect(drawerElem).toHaveTextContent('A repeating todo');
});
});
describe('Agenda', () => {
test('renders Agenda for an Org file', () => {
// Agenda is not visible by default
expect(queryByText('Agenda')).toBeFalsy();
expect(queryByText('Day')).toBeFalsy();
expect(queryByText('Month')).toBeFalsy();
expect(queryByText('A scheduled todo item')).toBeFalsy();
fireEvent.click(getByTitle('Show agenda'));
expect(queryByText('Agenda')).toBeTruthy();
expect(queryByText('Day')).toBeTruthy();
expect(queryByText('Month')).toBeTruthy();
expect(queryAllByText('A todo item with schedule and deadline')).toBeTruthy();
});
test('Agenda starts on Monday by default', () => {
fireEvent.click(getByTitle('Show agenda'));
expect(container.querySelectorAll('.agenda-day__title__day-name')[0]).toHaveTextContent(
'Monday'
);
});
test('Clicking a TODO within the agenda highlights it in the main view', () => {
expect(queryByText('A todo item with schedule and deadline')).toBeFalsy();
fireEvent.click(getByTitle('Show agenda'));
expect(queryByText('Agenda')).toBeTruthy();
fireEvent.click(queryAllByText('A todo item with schedule and deadline')[0]);
expect(queryByText('Agenda')).toBeFalsy();
expect(queryByText('A todo item with schedule and deadline')).toBeTruthy();
});
test('Clicking the Timestamp in a TODO within the agenda toggles from the date to the time', () => {
fireEvent.click(getByTitle('Show agenda'));
const timeSinceScheduled = formatDistanceToNow(new Date('2019-09-19'));
expect(queryByText(timeSinceScheduled)).toBeFalsy();
expect(queryByText('09/19')).toBeTruthy();
fireEvent.click(queryByText('09/19'));
expect(queryByText('09/19')).toBeFalsy();
expect(queryByText(`${timeSinceScheduled} ago`)).toBeTruthy();
});
test('Agenda shows only actionable TODOs, not with a DONE state', () => {
fireEvent.click(getByTitle('Show agenda'));
const drawerElem = getByTestId('drawer');
expect(drawerElem).not.toHaveTextContent("A headline that's done since a loong time");
expect(drawerElem).not.toHaveTextContent("A headline that's done a day earlier even");
expect(drawerElem).toHaveTextContent('A todo item with schedule and deadline');
expect(drawerElem).toHaveTextContent('A repeating todo');
});
});
describe('Link recognition', () => {
test('recognizes canonical format +xxxxxxxxx phone numbers', () => {
fireEvent.click(
queryByText('A header with a URL, mail address and phone number as content')
);
const elem = getAllByText('+49123456789');
// There's exactly one phone number
expect(elem.length).toEqual(1);
// And it renders as such
expect(elem[0]).toHaveAttribute('href', 'tel:+49123456789');
expect(elem[0]).toHaveTextContent('+49123456789');
});
test('recognizes phone numbers', () => {
fireEvent.click(
queryByText('A header with a URL, mail address and phone number as content')
);
const phone_numbers = [
// US
'123-456-7890',
'(123) 456-7890',
'123 456 7890',
'123.456.7890',
'+91 (123) 456-7890',
// Swiss
'0783268674',
'078 326 86 74',
'041783268675',
'0041783268674',
'+41783268676',
'+41783268677',
];
for (let i in phone_numbers) {
const phone_number = phone_numbers[i];
const elem = getAllByText(phone_number);
expect(elem.length).toEqual(1);
expect(elem[0]).toHaveAttribute('href', `tel:${phone_number}`);
expect(elem[0]).toHaveTextContent(phone_number);
}
});
test('does not recognize random numbers as phone numbers', () => {
fireEvent.click(
queryByText('A header with a URL, mail address and phone number as content')
);
const numbers = ['05 05 05'];
for (let i in numbers) {
const number = numbers[i];
const elem = getAllByText(number);
expect(elem.length).toEqual(1);
expect(elem[0]).not.toHaveAttribute('href', `tel:${number}`);
expect(elem[0]).toHaveTextContent(number);
}
});
test('recognizes URLs', () => {
fireEvent.click(
queryByText('A header with a URL, mail address and phone number as content')
);
const elem = getAllByText('https://foo.bar.baz/xyz?a=b&d#foo');
// There's exactly one such URL
expect(elem.length).toEqual(1);
// And it renders as such
expect(elem[0]).toHaveAttribute('href', 'https://foo.bar.baz/xyz?a=b&d#foo');
expect(elem[0]).toHaveTextContent('https://foo.bar.baz/xyz?a=b&d#foo');
});
describe('recognizes file: links', () => {
beforeEach(() => {
fireEvent.click(queryByText('A header with various links as content'));
});
test('relative link to .org file', () => {
const elem = getAllByText('an existing .org file in the same directory');
// There's exactly one such URL
expect(elem.length).toEqual(1);
// And it renders as such
expect(elem[0]).toHaveAttribute(
'data-target',
'/dir1/dir2/schedule_and_timestamps.org'
);
expect(elem[0]).toHaveTextContent('an existing .org file in the same directory');
});
test('relative link to subdir', () => {
const elem = getAllByText('subdir');
expect(elem.length).toEqual(1);
expect(elem[0]).toHaveAttribute('href', '/files/dir1/dir2/subdir');
expect(elem[0]).toHaveTextContent('subdir');
});
test('relative link to subdir/', () => {
const elem = getAllByText('subdir/');
expect(elem.length).toEqual(1);
expect(elem[0]).toHaveAttribute('href', '/files/dir1/dir2/subdir/');
expect(elem[0]).toHaveTextContent('subdir/');
});
test('relative link to fictitious .org file in subdir', () => {
const elem = getAllByText('a fictitious .org file in a sub-directory');
expect(elem.length).toEqual(1);
expect(elem[0]).toHaveAttribute('data-target', '/dir1/dir2/subdir/foo.org');
expect(elem[0]).toHaveTextContent('a fictitious .org file in a sub-directory');
});
test('relative link to fictitious .org file in a parent directory', () => {
const elem = getAllByText('a fictitious .org file in a parent directory');
expect(elem.length).toEqual(1);
expect(elem[0]).toHaveAttribute('data-target', '/dir1/foo.org_archive');
expect(elem[0]).toHaveTextContent('a fictitious .org file in a parent directory');
});
test('relative link to ../subdir', () => {
const elem = getAllByText('../subdir');
expect(elem.length).toEqual(1);
expect(elem[0]).toHaveAttribute('href', '/files/dir1/subdir');
expect(elem[0]).toHaveTextContent('../subdir');
});
test('relative link to ../subdir/', () => {
const elem = getAllByText('../subdir/');
expect(elem.length).toEqual(1);
expect(elem[0]).toHaveAttribute('href', '/files/dir1/subdir/');
expect(elem[0]).toHaveTextContent('../subdir/');
});
test('relative link to fictitious .org file in a grand-parent directory', () => {
const elem = getAllByText('a fictitious .org file in a grand-parent directory');
expect(elem.length).toEqual(1);
expect(elem[0]).toHaveAttribute('data-target', '/foo.org');
expect(elem[0]).toHaveTextContent('a fictitious .org file in a grand-parent directory');
});
test('relative link to fictitious .org file in a too-high ancestor directory', () => {
const elem = getAllByText('a fictitious .org file in a too-high ancestor directory');
expect(elem.length).toEqual(1);
expect(elem[0]).toHaveAttribute(
'data-target',
'../../../../too-high-to-access-file.org'
);
expect(elem[0]).toHaveTextContent(
'a fictitious .org file in a too-high ancestor directory'
);
});
test('relative link to too-high ancestor directory', () => {
const elem = getAllByText('a too-high ancestor directory');
expect(elem.length).toEqual(1);
// INFO: This file cannot be opened, because it is not
// visible from within the share given to organice.
expect(elem[0]).toHaveAttribute(
'data-target',
'../../../../too-high-to-access-directory'
);
expect(elem[0]).toHaveTextContent('a too-high ancestor directory');
});
test('absolute link to fictitious .org file in home directory', () => {
const elem = getAllByText('a fictitious .org file in home directory');
expect(elem.length).toEqual(1);
expect(elem[0]).toHaveAttribute('data-target', '~/foo/bar/baz.org');
// INFO: This file cannot really be opened, because
// organice doesn't know the directory structure of the
// user. I.e. the file link might be
// `file:~/Dropbox/org/things.org`. Then, organice would
// have to remove the `Dropbox` folder and try and see if
// it can find the file underneath.
expect(elem[0]).toHaveTextContent('a fictitious .org file in home directory');
});
test('absolute link to fictitious .org file', () => {
const elem = getAllByText('a fictitious .org file');
expect(elem.length).toEqual(1);
expect(elem[0]).toHaveAttribute('data-target', '/foo/bar/baz.org');
expect(elem[0]).toHaveTextContent('a fictitious .org file');
});
});
test('recognizes email addresses', () => {
fireEvent.click(
queryByText('A header with a URL, mail address and phone number as content')
);
const elem = getAllByText('foo.bar@baz.org');
// There's exactly one such email address
expect(elem.length).toEqual(1);
// And it renders as such
expect(elem[0]).toHaveAttribute('href', 'mailto:foo.bar@baz.org');
expect(elem[0]).toHaveTextContent('foo.bar@baz.org');
});
});
});
});
});