conservancy_beancount/tests/test_reports_spreadsheet.py

805 lines
29 KiB
Python
Raw Normal View History

2020-06-03 22:54:49 +00:00
"""test_reports_spreadsheet - Unit tests for spreadsheet classes"""
# Copyright © 2020 Brett Smith
# License: AGPLv3-or-later WITH Beancount-Plugin-Additional-Permission-1.0
2020-06-03 22:54:49 +00:00
#
# Full copyright and licensing details can be found at toplevel file
# LICENSE.txt in the repository.
2020-06-03 22:54:49 +00:00
2020-06-03 23:03:02 +00:00
import datetime
import io
import itertools
import re
2020-06-03 23:03:02 +00:00
2020-06-03 22:54:49 +00:00
import pytest
2020-06-03 23:03:02 +00:00
import babel.core
import babel.numbers
import odf.config
import odf.meta
2020-06-03 23:03:02 +00:00
import odf.number
import odf.style
import odf.table
import odf.text
from decimal import Decimal
2020-06-03 22:54:49 +00:00
from . import testutil
from conservancy_beancount import rtutil
2020-06-03 22:54:49 +00:00
from conservancy_beancount.reports import core
2020-06-03 23:03:02 +00:00
EN_US = babel.core.Locale('en', 'US')
XML_NAMES_LIST = [None, 'ce2', 'xml_testname']
XML_NAMES = itertools.cycle(XML_NAMES_LIST)
CURRENCY_CELL_DATA = [
(Decimal('10.101010'), 'BRL'),
(Decimal('-50.50'), 'GBP'),
]
LINK_CELL_DATA = [
'https://example.org',
('https://example.net', None),
('https://example.com', 'Example Site'),
]
NUMERIC_CELL_DATA = [
42,
42.42,
Decimal('42.42'),
]
STRING_CELL_DATA = [
'Example String',
LINK_CELL_DATA[0],
]
2020-06-03 22:54:49 +00:00
class BaseTester(core.BaseSpreadsheet[tuple, str]):
def __init__(self):
self.start_call = None
self.end_call = None
self.started_sections = []
self.ended_sections = []
self.written_rows = []
def section_key(self, row):
return row[0]
def start_spreadsheet(self):
self.start_call = self.started_sections.copy()
def start_section(self, key):
self.started_sections.append(key)
def end_section(self, key):
self.ended_sections.append(key)
def end_spreadsheet(self):
self.end_call = self.ended_sections.copy()
def write_row(self, key):
self.written_rows.append(key)
2020-06-03 23:03:02 +00:00
class ODSTester(core.BaseODS[tuple, str]):
def section_key(self, row):
return row[0]
2020-06-03 22:54:49 +00:00
@pytest.fixture
def spreadsheet():
return BaseTester()
2020-06-03 23:03:02 +00:00
@pytest.fixture
def ods_writer():
retval = ODSTester()
retval.locale = EN_US
return retval
def get_children(parent, child_type, **kwargs):
return [elem for elem in parent.getElementsByType(child_type)
if all(elem.getAttribute(k) == v for k, v in kwargs.items())]
def get_child(parent, child_type, index=-1, **kwargs):
try:
return get_children(parent, child_type, **kwargs)[index]
except IndexError:
raise ValueError("no matching child found") from None
def iter_text(parent):
for child in parent.childNodes:
if isinstance(child, odf.element.Text):
yield child.data
else:
yield from iter_text(child)
def get_text(parent, joiner=''):
return joiner.join(iter_text(parent))
def check_currency_style(curr_style):
child_names = {child.tagName for child in curr_style.childNodes}
assert odf.number.Number().tagName in child_names
assert len(child_names) > 1
2020-06-03 22:54:49 +00:00
def test_spreadsheet(spreadsheet):
rows = [(ch, ii) for ii, ch in enumerate('aabbcc', 1)]
spreadsheet.write(iter(rows))
assert spreadsheet.written_rows == rows
assert spreadsheet.ended_sections == spreadsheet.started_sections
assert spreadsheet.started_sections == list('abc')
assert spreadsheet.start_call == []
assert spreadsheet.end_call == spreadsheet.ended_sections
def test_empty_spreadsheet(spreadsheet):
empty_list = []
spreadsheet.write(iter(empty_list))
assert spreadsheet.start_call == empty_list
assert spreadsheet.end_call == empty_list
assert spreadsheet.started_sections == empty_list
assert spreadsheet.ended_sections == empty_list
assert spreadsheet.written_rows == empty_list
def test_one_section_spreadsheet(spreadsheet):
rows = [('A', n) for n in range(1, 4)]
spreadsheet.write(iter(rows))
assert spreadsheet.written_rows == rows
assert spreadsheet.ended_sections == spreadsheet.started_sections
assert spreadsheet.started_sections == list('A')
assert spreadsheet.start_call == []
assert spreadsheet.end_call == spreadsheet.ended_sections
2020-06-03 23:03:02 +00:00
def test_ods_writer(ods_writer):
rows = [(ch, ii) for ii, ch in enumerate('aabbcc', 1)]
ods_writer.write(iter(rows))
sheets = ods_writer.document.getElementsByType(odf.table.Table)
assert len(sheets) == 1
for exp_row, act_row in zip(rows, testutil.ODSCell.from_sheet(sheets[0])):
expected1, expected2 = exp_row
actual1, actual2 = act_row
assert actual1.value_type == 'string'
assert actual1.text == expected1
assert actual2.value_type == 'float'
assert actual2.value == expected2
assert actual2.text == str(expected2)
@pytest.mark.parametrize('save_type', ['file', 'path'])
def test_ods_writer_save(tmp_path, save_type):
rows = list(zip('ABC', 'abc'))
ods_writer = ODSTester()
ods_writer.write(iter(rows))
if save_type == 'file':
ods_output = io.BytesIO()
ods_writer.save_file(ods_output)
ods_output.seek(0)
else:
ods_output = tmp_path / 'SavePathTest.ods'
ods_writer.save_path(ods_output)
for exp_row, act_row in zip(rows, testutil.ODSCell.from_ods_file(ods_output)):
assert len(exp_row) == len(act_row)
for expected, actual in zip(exp_row, act_row):
assert actual.value_type == 'string'
assert actual.value is None
assert actual.text == expected
def test_ods_writer_use_sheet(ods_writer):
names = ['One', 'Two']
for name in names:
ods_writer.use_sheet(name)
ods_writer.write([(name,)])
ods_writer.use_sheet('End')
sheets = ods_writer.document.getElementsByType(odf.table.Table)
assert len(sheets) == len(names) + 1
for name, sheet in zip(names, sheets):
texts = [cell.text for row in testutil.ODSCell.from_sheet(sheet)
for cell in row]
assert texts == [name]
def test_ods_writer_use_sheet_returns_to_prior_sheets(ods_writer):
names = ['One', 'Two']
sheets = []
for name in names:
sheets.append(ods_writer.use_sheet(name))
ods_writer.write([(name,)])
for name, expected in zip(names, sheets):
actual = ods_writer.use_sheet(name)
assert actual is expected
texts = [cell.text for row in testutil.ODSCell.from_sheet(actual)
for cell in row]
assert texts == [name]
def test_ods_writer_use_sheet_discards_unused_sheets(ods_writer):
ods_writer.use_sheet('Three')
ods_writer.use_sheet('Two')
ods_writer.use_sheet('One')
sheets = ods_writer.document.getElementsByType(odf.table.Table)
assert len(sheets) == 1
assert sheets[0].getAttribute('name') == 'One'
@pytest.mark.parametrize('width,expect_name', [
('.750', 'col_0_75in'),
(2, 'col_2in'),
('2.2in', 'col_2_2in'),
(3.5, 'col_3_5in'),
('4cm', 'col_4cm'),
])
def test_ods_column_style(ods_writer, width, expect_name):
style = ods_writer.column_style(width)
assert style.getAttribute('name') == expect_name
assert style.getAttribute('family') == 'table-column'
curr_style = get_child(
ods_writer.document.automaticstyles,
odf.style.Style,
name=expect_name,
)
assert get_child(
curr_style,
odf.style.TableColumnProperties,
columnwidth=expect_name[4:].replace('_', '.'),
)
def test_ods_column_style_caches(ods_writer):
int_width = ods_writer.column_style('1in')
float_width = ods_writer.column_style('1.00in')
assert int_width is float_width
@pytest.mark.parametrize('width', [
'1mi',
'0in',
'-1cm',
'in',
'.cm',
])
def test_ods_column_style_invalid_width(ods_writer, width):
with pytest.raises(ValueError):
ods_writer.column_style(width)
2020-06-03 23:03:02 +00:00
@pytest.mark.parametrize('currency_code', [
'USD',
'EUR',
'BRL',
])
def test_ods_currency_style(ods_writer, currency_code):
style = ods_writer.currency_style(currency_code)
assert style.getAttribute('family') == 'table-cell'
curr_style = get_child(
ods_writer.document.styles,
odf.number.CurrencyStyle,
name=style.getAttribute('datastylename'),
)
check_currency_style(curr_style)
mappings = get_children(curr_style, odf.style.Map)
assert mappings
for mapping in mappings:
check_currency_style(get_child(
ods_writer.document.styles,
odf.number.CurrencyStyle,
name=mapping.getAttribute('applystylename'),
))
def test_ods_currency_style_caches(ods_writer):
expected = ods_writer.currency_style('USD')
_ = ods_writer.currency_style('EUR')
actual = ods_writer.currency_style('USD')
assert actual is expected
def test_ods_currency_style_cache_considers_properties(ods_writer):
bold_text = odf.style.TextProperties(fontweight='bold')
plain = ods_writer.currency_style('USD')
bold = ods_writer.currency_style('USD', positive_properties=bold_text)
assert plain is not bold
assert plain.getAttribute('name') != bold.getAttribute('name')
assert plain.getAttribute('datastylename') != bold.getAttribute('datastylename')
@pytest.mark.parametrize('attr_name,child_type,checked_attr', [
('style_bold', odf.style.TextProperties, 'fontweight'),
('style_centertext', odf.style.ParagraphProperties, 'textalign'),
('style_endtext', odf.style.ParagraphProperties, 'textalign'),
('style_starttext', odf.style.ParagraphProperties, 'textalign'),
('style_total', odf.style.TableCellProperties, 'bordertop'),
('style_endtotal', odf.style.TableCellProperties, 'borderbottom'),
('style_bottomline', odf.style.TableCellProperties, 'borderbottom'),
2020-06-03 23:03:02 +00:00
])
def test_ods_writer_style(ods_writer, attr_name, child_type, checked_attr):
root = ods_writer.document.styles
2020-06-03 23:03:02 +00:00
style = getattr(ods_writer, attr_name)
actual = get_child(root, odf.style.Style, name=style.getAttribute('name'))
2020-06-03 23:03:02 +00:00
assert actual is style
child = get_child(actual, child_type)
assert child.getAttribute(checked_attr)
@pytest.mark.parametrize('hexcolor', ['#0000ff', '#66cc99'])
def test_ods_writer_bgcolor_style(ods_writer, hexcolor):
style = ods_writer.bgcolor_style(hexcolor)
props, = style.childNodes
assert props.getAttribute('backgroundcolor') == hexcolor
def test_ods_writer_bgcolor_style_caches(ods_writer):
style1 = ods_writer.bgcolor_style('#008800')
assert ods_writer.bgcolor_style('#008800') is style1
@pytest.mark.parametrize('edges,width,style,color', [
(core.Border.TOP,
'5px', 'solid', '#ff0000'),
(core.Border.RIGHT | core.Border.LEFT,
'2pt', 'dashed', '#00ff00'),
(core.Border.BOTTOM | core.Border.RIGHT | core.Border.LEFT,
'1em', 'dotted', '#0000ff'),
(core.Border.TOP | core.Border.BOTTOM | core.Border.RIGHT | core.Border.LEFT,
'1cm', 'thick', '#aaaaaa'),
])
def test_ods_writer_border_style(ods_writer, edges, width, style, color):
actual = ods_writer.border_style(edges, width, style, color)
props, = actual.childNodes
attr_s = f'{width} {style} {color}'
for edge_exp, edge_name in enumerate(['top', 'right', 'bottom', 'left']):
expected = attr_s if edges & (2 ** edge_exp) else None
assert props.getAttribute(f'border{edge_name}') == expected
def test_ods_writer_border_style_caches(ods_writer):
expected = ods_writer.border_style(core.Border.TOP)
width, style, color = expected.childNodes[0].getAttribute('bordertop').split()
actual = ods_writer.border_style(core.Border.TOP, width, style, color)
assert actual is expected
@pytest.mark.parametrize('argname,val1,val2', [
('edges', core.Border.TOP, core.Border.LEFT),
('edges', core.Border.TOP, core.Border.TOP | core.Border.BOTTOM),
('style', 'solid', 'dashed'),
('width', '1px', '1em'),
('width', '1px', '2px'),
('color', '#0000fe', '#0000ff'),
])
def test_ods_writer_border_no_caching(ods_writer, argname, val1, val2):
kwargs = {'edges': core.Border.TOP}
kwargs[argname] = val1
style1 = ods_writer.border_style(**kwargs)
kwargs[argname] = val2
style2 = ods_writer.border_style(**kwargs)
assert style1 is not style2
2020-06-03 23:03:02 +00:00
def test_ods_writer_merge_styles(ods_writer):
border_style = ods_writer.border_style(core.Border.BOTTOM)
style = ods_writer.merge_styles(ods_writer.style_bold, border_style)
2020-06-03 23:03:02 +00:00
actual = get_child(
ods_writer.document.styles,
odf.style.Style,
name=style.getAttribute('name'),
)
assert actual is style
assert actual.getAttribute('family') == 'table-cell'
text_props = get_child(actual, odf.style.TextProperties)
assert text_props.getAttribute('fontweight') == 'bold'
cell_props = get_child(actual, odf.style.TableCellProperties)
assert cell_props.getAttribute('borderbottom')
def test_ods_writer_merge_styles_with_children_and_attributes(ods_writer):
jpy_style = ods_writer.currency_style('JPY')
style = ods_writer.merge_styles(ods_writer.style_bold, jpy_style)
actual = get_child(
ods_writer.document.styles,
odf.style.Style,
name=style.getAttribute('name'),
)
assert actual is style
assert actual.getAttribute('family') == 'table-cell'
assert actual.getAttribute('datastylename') == jpy_style.getAttribute('datastylename')
text_props = get_child(actual, odf.style.TextProperties)
assert text_props.getAttribute('fontweight') == 'bold'
def test_ods_writer_merge_styles_caches(ods_writer):
sources = [ods_writer.style_bold, ods_writer.style_centertext]
2020-06-03 23:03:02 +00:00
style1 = ods_writer.merge_styles(*sources)
style2 = ods_writer.merge_styles(*reversed(sources))
assert style1 is style2
assert get_child(
ods_writer.document.styles,
odf.style.Style,
name=style1.getAttribute('name'),
)
def test_ods_writer_layer_merge_styles(ods_writer):
usd_style = ods_writer.currency_style('USD')
border_style = ods_writer.border_style(core.Border.BOTTOM)
layer1 = ods_writer.merge_styles(ods_writer.style_bold, border_style)
2020-06-03 23:03:02 +00:00
layer2 = ods_writer.merge_styles(layer1, usd_style)
style_name = layer2.getAttribute('name')
assert style_name.count('Merge_') == 1
actual = get_child(
ods_writer.document.styles,
odf.style.Style,
name=style_name,
)
assert actual is layer2
assert actual.getAttribute('family') == 'table-cell'
assert actual.getAttribute('datastylename') == usd_style.getAttribute('datastylename')
text_props = get_child(actual, odf.style.TextProperties)
assert text_props.getAttribute('fontweight') == 'bold'
cell_props = get_child(actual, odf.style.TableCellProperties)
assert cell_props.getAttribute('borderbottom')
def test_ods_writer_merge_one_style(ods_writer):
actual = ods_writer.merge_styles(None, ods_writer.style_bold)
assert actual is ods_writer.style_bold
def test_ods_writer_merge_no_styles(ods_writer):
assert ods_writer.merge_styles() is None
def test_ods_writer_merge_nonexistent_style(ods_writer):
name = 'Non Existent Style'
with pytest.raises(ValueError, match=repr(name)):
ods_writer.merge_styles(ods_writer.style_bold, name)
def test_ods_writer_merge_conflicting_styles(ods_writer):
sources = [ods_writer.currency_style(code) for code in ['USD', 'EUR']]
with pytest.raises(ValueError, match='conflicting datastylename'):
ods_writer.merge_styles(*sources)
def test_ods_writer_date_style(ods_writer):
data_style_name = ods_writer.style_date.getAttribute('datastylename')
actual = get_child(
ods_writer.document.styles,
odf.style.Style,
family='table-cell',
datastylename=data_style_name,
)
assert actual is ods_writer.style_date
data_style = get_child(
ods_writer.document.styles,
odf.number.DateStyle,
name=data_style_name,
)
assert len(data_style.childNodes) == 5
year, t1, month, t2, day = data_style.childNodes
assert year.qname[1] == 'year'
assert year.getAttribute('style') == 'long'
assert get_text(t1) == '-'
assert month.qname[1] == 'month'
assert month.getAttribute('style') == 'long'
assert get_text(t2) == '-'
assert day.qname[1] == 'day'
assert day.getAttribute('style') == 'long'
@pytest.mark.parametrize('method_name,split_name,side_name', [
('lock_first_row', 'Vertical', 'Bottom'),
('lock_first_column', 'Horizontal', 'Right'),
])
def test_ods_lock_first_cells(ods_writer, method_name, split_name, side_name):
getattr(ods_writer, method_name)()
2020-06-03 23:03:02 +00:00
view_settings = get_child(
ods_writer.document.settings,
odf.config.ConfigItemSet,
name='ooo:view-settings',
)
views = get_child(view_settings, odf.config.ConfigItemMapIndexed, name='Views')
view1 = get_child(views, odf.config.ConfigItemMapEntry, index=0)
config_map = get_child(view1, odf.config.ConfigItemMapNamed, name='Tables')
sheet_name = ods_writer.sheet.getAttribute('name')
config_entry = get_child(config_map, odf.config.ConfigItemMapEntry, name=sheet_name)
for name, ctype, value in [
(f'Position{side_name}', 'int', '1'),
(f'{split_name}SplitMode', 'short', '2'),
(f'{split_name}SplitPosition', 'short', '1'),
2020-06-03 23:03:02 +00:00
]:
child = get_child(config_entry, odf.config.ConfigItem, name=name)
assert child.getAttribute('type') == ctype
assert child.firstChild.data == value
@pytest.mark.parametrize('arg', [
None,
'Target Sheet',
odf.table.Table(name='Target Sheet'),
])
def test_ods_set_open_sheet(ods_writer, arg):
ods_writer.use_sheet('Start Sheet' if arg else 'Target Sheet')
ods_writer.set_open_sheet(arg)
view_settings = get_child(
ods_writer.document.settings,
odf.config.ConfigItemSet,
name='ooo:view-settings',
)
views = get_child(view_settings, odf.config.ConfigItemMapIndexed, name='Views')
view1 = get_child(views, odf.config.ConfigItemMapEntry, index=0)
actual = get_child(view1, odf.config.ConfigItem, name='ActiveTable')
assert actual.text == 'Target Sheet'
2020-06-03 23:03:02 +00:00
@pytest.mark.parametrize('style_name', XML_NAMES_LIST)
def test_ods_writer_add_row(ods_writer, style_name):
cell1 = ods_writer.string_cell('one')
cell2 = ods_writer.float_cell(42.0)
row = ods_writer.add_row(cell1, cell2, defaultcellstylename=style_name)
assert ods_writer.sheet.lastChild is row
assert row.getAttribute('defaultcellstylename') == style_name
assert row.firstChild is cell1
assert row.lastChild is cell2
def test_ods_writer_add_row_single_cell(ods_writer):
cell = ods_writer.multilink_cell(LINK_CELL_DATA[:1])
row = ods_writer.add_row(cell)
assert ods_writer.sheet.lastChild is row
assert row.firstChild is cell
assert row.lastChild is cell
def test_ods_writer_add_row_empty(ods_writer):
row = ods_writer.add_row(stylename='blank')
assert ods_writer.sheet.lastChild is row
assert row.firstChild is None
assert row.getAttribute('stylename') == 'blank'
@pytest.mark.parametrize('col_count', range(3))
def test_ods_writer_row_count(ods_writer, col_count):
for _ in range(col_count):
ods_writer.sheet.addElement(odf.table.TableColumn())
assert ods_writer.row_count() == 0
for expected in range(1, 4):
ods_writer.sheet.addElement(odf.table.TableRow())
assert ods_writer.row_count() == expected
2020-06-03 23:03:02 +00:00
def test_ods_writer_balance_cell_empty(ods_writer):
balance = core.Balance()
cell = ods_writer.balance_cell(balance)
assert cell.value_type != 'string'
assert float(cell.value) == 0
def test_ods_writer_balance_cell_single_currency(ods_writer):
number = 250
currency = 'EUR'
balance = core.Balance([testutil.Amount(number, currency)])
cell = ods_writer.balance_cell(balance)
assert cell.value_type == 'currency'
assert Decimal(cell.value) == number
assert cell.text == babel.numbers.format_currency(
number, currency, locale=EN_US, format_type='accounting',
)
def test_ods_writer_balance_cell_multi_currency(ods_writer):
amounts = [testutil.Amount(num, code) for num, code in [
(2500, 'RUB'),
(3500, 'BRL'),
]]
balance = core.Balance(amounts)
cell = ods_writer.balance_cell(balance)
assert cell.text == '2,500.00 RUB\0R$3,500.00'
2020-06-03 23:03:02 +00:00
@pytest.mark.parametrize('cell_source,style_name', testutil.combine_values(
CURRENCY_CELL_DATA,
XML_NAMES,
))
def test_ods_writer_currency_cell(ods_writer, cell_source, style_name):
cell = ods_writer.currency_cell(cell_source, stylename=style_name)
number, currency = cell_source
assert cell.getAttribute('valuetype') == 'currency'
assert cell.getAttribute('value') == str(number)
assert cell.getAttribute('stylename') == style_name
expected = babel.numbers.format_currency(
number, currency, locale=EN_US, format_type='accounting',
)
assert get_text(cell) == expected
@pytest.mark.parametrize('currency', [
'EUR',
'CHF',
'GBP',
])
def test_ods_writer_currency_cell_default_style(ods_writer, currency):
amount = testutil.Amount(1000, currency)
expected_stylename = ods_writer.currency_style(currency).getAttribute('name')
cell = ods_writer.currency_cell(amount)
assert cell.getAttribute('valuetype') == 'currency'
assert cell.getAttribute('value') == '1000'
assert cell.getAttribute('stylename') == expected_stylename
2020-06-03 23:03:02 +00:00
@pytest.mark.parametrize('date,style_name', testutil.combine_values(
[datetime.date(1980, 2, 5), datetime.date(2030, 10, 30)],
XML_NAMES_LIST,
))
def test_ods_writer_date_cell(ods_writer, date, style_name):
if style_name is None:
expect_style = ods_writer.style_date.getAttribute('name')
cell = ods_writer.date_cell(date)
else:
expect_style = style_name
cell = ods_writer.date_cell(date, stylename=style_name)
date_s = date.isoformat()
assert cell.getAttribute('valuetype') == 'date'
assert cell.getAttribute('datevalue') == date_s
assert cell.getAttribute('stylename') == expect_style
assert get_text(cell) == date_s
@pytest.mark.parametrize('cell_source,style_name', testutil.combine_values(
NUMERIC_CELL_DATA,
XML_NAMES,
))
def test_ods_writer_float_cell(ods_writer, cell_source, style_name):
cell = ods_writer.float_cell(cell_source, stylename=style_name)
assert cell.getAttribute('valuetype') == 'float'
assert cell.getAttribute('stylename') == style_name
expected = str(cell_source)
assert cell.getAttribute('value') == expected
assert get_text(cell) == expected
def test_ods_writer_meta_links_cell(ods_writer):
rt_client = testutil.RTClient()
ods_writer.rt_wrapper = rtutil.RT(rt_client)
rt_url = rt_client.DEFAULT_URL[:-10]
meta_links = [
'rt://ticket/1',
'rt://ticket/2/attachments/9',
'rt:1/5',
'Invoices/0123.pdf',
'Invoice #789.pdf',
]
cell = ods_writer.meta_links_cell(meta_links, stylename='meta1')
assert cell.getAttribute('valuetype') == 'string'
assert cell.getAttribute('stylename') == 'meta1'
children = iter(get_children(cell, odf.text.A))
child = next(children)
assert child.getAttribute('type') == 'simple'
expect_url = f'{rt_url}/Ticket/Display.html?id=1'
assert child.getAttribute('href') == expect_url
assert get_text(child) == 'rt:1'
child = next(children)
assert child.getAttribute('type') == 'simple'
expect_url = f'{rt_url}/Ticket/Display.html?id=2#txn-7'
assert child.getAttribute('href') == expect_url
assert get_text(child) == 'rt:2/9'
child = next(children)
assert child.getAttribute('type') == 'simple'
expect_url = f'{rt_url}/Ticket/Attachment/1/5/photo.jpg'
assert child.getAttribute('href') == expect_url
assert get_text(child) == 'photo.jpg'
child = next(children)
assert child.getAttribute('type') == 'simple'
expect_url = f'../{meta_links[3]}'
assert child.getAttribute('href') == expect_url
assert get_text(child) == '0123.pdf'
child = next(children)
assert child.getAttribute('type') == 'simple'
assert child.getAttribute('href') == '../Invoice%20%23789.pdf'
assert get_text(child) == 'Invoice #789.pdf'
2020-06-03 23:03:02 +00:00
def test_ods_writer_multiline_cell(ods_writer):
cell = ods_writer.multiline_cell(iter(STRING_CELL_DATA))
assert cell.getAttribute('valuetype') == 'string'
children = get_children(cell, odf.text.P)
for expected, child in itertools.zip_longest(STRING_CELL_DATA, children):
assert get_text(child) == expected
@pytest.mark.parametrize('cell_source,style_name', testutil.combine_values(
LINK_CELL_DATA,
XML_NAMES,
))
def test_ods_writer_multilink_singleton(ods_writer, cell_source, style_name):
cell = ods_writer.multilink_cell([cell_source], stylename=style_name)
assert cell.getAttribute('valuetype') == 'string'
assert cell.getAttribute('stylename') == style_name
try:
href, text = cell_source
except ValueError:
href = cell_source
text = None
anchor = get_child(cell, odf.text.A, type='simple', href=href)
assert get_text(anchor) == (text or href)
2020-06-03 23:03:02 +00:00
def test_ods_writer_multilink_cell(ods_writer):
cell = ods_writer.multilink_cell(iter(LINK_CELL_DATA))
assert cell.getAttribute('valuetype') == 'string'
children = get_children(cell, odf.text.A)
for source, child in itertools.zip_longest(LINK_CELL_DATA, children):
try:
href, text = source
except ValueError:
href = source
text = None
assert child.getAttribute('type') == 'simple'
assert child.getAttribute('href') == href
assert get_text(child) == (text or href)
2020-06-03 23:03:02 +00:00
@pytest.mark.parametrize('cell_source,style_name', testutil.combine_values(
STRING_CELL_DATA,
XML_NAMES,
))
def test_ods_writer_string_cell(ods_writer, cell_source, style_name):
cell = ods_writer.string_cell(cell_source, stylename=style_name)
assert cell.getAttribute('valuetype') == 'string'
assert cell.getAttribute('stylename') == style_name
assert get_text(cell) == str(cell_source)
def test_ods_writer_copy_element(ods_writer):
child1 = odf.text.P()
child1.addElement(odf.text.A(href='linkhref', text='linktext'))
child2 = odf.text.P(text='para2')
cell = odf.table.TableCell(stylename='cellsty')
cell.addElement(child1)
cell.addElement(child2)
actual = ods_writer.copy_element(cell)
assert actual is not cell
assert actual.getAttribute('stylename') == 'cellsty'
actual1, actual2 = actual.childNodes
assert actual1 is not child1
assert actual2 is not child2
actual_a, = actual1.childNodes
assert actual_a.getAttribute('href') == 'linkhref'
assert actual_a.text == 'linktext'
assert actual2.text == 'para2'
def test_ods_writer_default_properties(ods_writer):
meta = ods_writer.document.meta
yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
creation_date_elem = get_child(meta, odf.meta.CreationDate)
creation_date = datetime.datetime.strptime(
creation_date_elem.text, '%Y-%m-%dT%H:%M:%S.%f',
)
assert creation_date > yesterday
generator = get_child(meta, odf.meta.Generator)
assert re.match(r'conservancy_beancount/\d+\.\d+', generator.text)
def test_ods_writer_set_properties(ods_writer):
ctime = datetime.datetime(2009, 9, 19, 9, 49, 59)
ods_writer.set_properties(created=ctime, generator='testgen')
meta = ods_writer.document.meta
creation_date_elem = get_child(meta, odf.meta.CreationDate)
assert creation_date_elem.text == ctime.isoformat()
generator = get_child(meta, odf.meta.Generator)
assert re.match(r'testgen/\d+\.\d+', generator.text)
@pytest.mark.parametrize('value,exptype', [
(1, 'float'),
(12.34, 'float'),
(Decimal('5.99'), 'float'),
(datetime.date(2009, 8, 17), 'date'),
(datetime.datetime(2009, 8, 17, 18, 38, 58), 'date'),
(True, 'boolean'),
(False, 'boolean'),
('foo', None)
])
def test_ods_writer_set_custom_property(ods_writer, value, exptype):
cprop = ods_writer.set_custom_property('cprop', value)
assert cprop.getAttribute('name') == 'cprop'
assert cprop.getAttribute('valuetype') == exptype
if exptype == 'boolean':
expected = str(value).lower()
elif exptype == 'date':
expected = value.isoformat()
else:
expected = str(value)
assert cprop.text == expected
def test_ods_writer_set_common_properties(ods_writer):
ods_writer.set_common_properties()
get_child(ods_writer.document.meta, odf.meta.UserDefined, name='ReportCommand')
def test_ods_writer_common_repo_properties(ods_writer):
repo = testutil.TestRepo('abcd1234', False)
ods_writer.set_common_properties(repo)
meta = ods_writer.document.meta
sha_prop = get_child(meta, odf.meta.UserDefined, name='GitSHA')
assert sha_prop.text == 'abcd1234'
dirty_prop = get_child(meta, odf.meta.UserDefined, name='GitDirty')
assert dirty_prop.text == 'false'
def test_ods_writer_common_command(ods_writer):
ods_writer.set_common_properties(command=['testcmd', 'testarg*'])
cmd_prop = get_child(ods_writer.document.meta, odf.meta.UserDefined, name='ReportCommand')
assert cmd_prop.text == 'testcmd \'testarg*\''
@pytest.mark.parametrize('text,when,parent', itertools.product(
[None, 'comment text'],
[None, datetime.datetime.now()],
[True, False],
))
def test_ods_add_annotation(ods_writer, text, when, parent):
start_time = datetime.datetime.now()
parent = odf.table.TableCell() if parent else None
actual = ods_writer.add_annotation(text, when, parent)
if text is None:
assert actual.firstChild is actual.lastChild
else:
assert actual.lastChild.text == text
if when is None:
assert actual.firstChild.text >= start_time.isoformat(timespec='seconds')
else:
assert actual.firstChild.text == when.isoformat(timespec='seconds')
if parent is not None:
assert parent.lastChild is actual