2020-06-03 22:54:49 +00:00
|
|
|
"""test_reports_spreadsheet - Unit tests for spreadsheet classes"""
|
|
|
|
# Copyright © 2020 Brett Smith
|
2021-01-08 21:57:43 +00:00
|
|
|
# License: AGPLv3-or-later WITH Beancount-Plugin-Additional-Permission-1.0
|
2020-06-03 22:54:49 +00:00
|
|
|
#
|
2021-01-08 21:57:43 +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
|
2020-07-29 20:48:16 +00:00
|
|
|
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
|
2020-07-29 20:48:16 +00:00
|
|
|
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
|
|
|
|
|
2020-06-12 21:10:25 +00:00
|
|
|
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'
|
|
|
|
|
2020-06-17 02:41:13 +00:00
|
|
|
@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'),
|
2020-08-22 16:33:06 +00:00
|
|
|
('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):
|
2020-06-17 02:41:13 +00:00
|
|
|
root = ods_writer.document.styles
|
2020-06-03 23:03:02 +00:00
|
|
|
style = getattr(ods_writer, attr_name)
|
2020-06-14 12:18:59 +00:00
|
|
|
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)
|
|
|
|
|
2020-06-28 03:08:23 +00:00
|
|
|
@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):
|
2020-06-28 03:12:35 +00:00
|
|
|
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):
|
2020-06-28 03:12:35 +00:00
|
|
|
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')
|
2020-06-28 03:12:35 +00:00
|
|
|
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'
|
|
|
|
|
2020-06-29 13:20:59 +00:00
|
|
|
@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 [
|
2020-06-29 13:20:59 +00:00
|
|
|
(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
|
|
|
|
|
2020-06-29 13:38:04 +00:00
|
|
|
@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'
|
|
|
|
|
|
|
|
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)
|
2020-09-04 19:29:10 +00:00
|
|
|
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
|
|
|
|
|
2020-06-15 18:09:42 +00:00
|
|
|
@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
|
|
|
|
|
2020-06-12 21:10:25 +00:00
|
|
|
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',
|
2020-09-05 18:47:34 +00:00
|
|
|
'Invoice #789.pdf',
|
2020-06-12 21:10:25 +00:00
|
|
|
]
|
|
|
|
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'
|
2020-09-05 18:47:34 +00:00
|
|
|
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-12 21:10:25 +00:00
|
|
|
|
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)
|
2020-06-12 20:42:54 +00:00
|
|
|
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
|
2020-06-12 20:42:54 +00:00
|
|
|
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)
|
2020-06-27 21:36:02 +00:00
|
|
|
|
|
|
|
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'
|
2020-07-29 20:48:16 +00:00
|
|
|
|
|
|
|
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
|
2020-07-29 21:22:09 +00:00
|
|
|
|
|
|
|
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*\''
|