c2851f5cc0
This was already done correctly in RT links because rtutil takes care of the quoting. The fact that we weren't doing it for file links was an oversight.
774 lines
28 KiB
Python
774 lines
28 KiB
Python
"""test_reports_spreadsheet - Unit tests for spreadsheet classes"""
|
|
# Copyright © 2020 Brett Smith
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
import datetime
|
|
import io
|
|
import itertools
|
|
import re
|
|
|
|
import pytest
|
|
|
|
import babel.core
|
|
import babel.numbers
|
|
import odf.config
|
|
import odf.meta
|
|
import odf.number
|
|
import odf.style
|
|
import odf.table
|
|
import odf.text
|
|
|
|
from decimal import Decimal
|
|
|
|
from . import testutil
|
|
|
|
from conservancy_beancount import rtutil
|
|
from conservancy_beancount.reports import core
|
|
|
|
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],
|
|
]
|
|
|
|
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)
|
|
|
|
|
|
class ODSTester(core.BaseODS[tuple, str]):
|
|
def section_key(self, row):
|
|
return row[0]
|
|
|
|
|
|
@pytest.fixture
|
|
def spreadsheet():
|
|
return BaseTester()
|
|
|
|
@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
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
@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'),
|
|
])
|
|
def test_ods_writer_style(ods_writer, attr_name, child_type, checked_attr):
|
|
root = ods_writer.document.styles
|
|
style = getattr(ods_writer, attr_name)
|
|
actual = get_child(root, odf.style.Style, name=style.getAttribute('name'))
|
|
assert actual is style
|
|
child = get_child(actual, child_type)
|
|
assert child.getAttribute(checked_attr)
|
|
|
|
@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
|
|
|
|
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)
|
|
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]
|
|
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)
|
|
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)()
|
|
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'),
|
|
]:
|
|
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'
|
|
|
|
@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)
|
|
assert cell.text == '2,500.00 RUB\0R$3,500.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
|
|
|
|
@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'
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
@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*\''
|