144 lines
6.9 KiB
Python
144 lines
6.9 KiB
Python
import json
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
import subprocess
|
|
import unittest
|
|
|
|
from lxml import etree as ET
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
import test_expense_template as fixtures
|
|
from expense_preview import prepare_preview
|
|
from expense_template import Template, encoded, tag
|
|
from spreadsheet_layout import formatted, read_layout
|
|
|
|
|
|
class SpreadsheetLayoutTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.fixture = fixtures.ExpenseTemplateTests()
|
|
self.fixture.setUp()
|
|
self.addCleanup(self.fixture.tearDown)
|
|
|
|
def test_fresh_import_does_not_read_system_mime_database(self):
|
|
code = """
|
|
import sys
|
|
def audit(event, args):
|
|
if event == 'open' and str(args[0]).endswith('mime.types'):
|
|
raise PermissionError('System MIME database is unavailable in the app sandbox')
|
|
sys.addaudithook(audit)
|
|
import spreadsheet_layout
|
|
"""
|
|
result = subprocess.run([sys.executable, '-c', code],
|
|
cwd=Path(__file__).resolve().parents[1], capture_output=True, text=True)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
|
|
def test_actual_workbook_geometry_styles_and_values(self):
|
|
output, _ = self.fixture.export(payee=dict(recipient='测试收款人', bankName='测试银行测试支行',
|
|
accountNumber='001234567890'))
|
|
result = prepare_preview(output)
|
|
before = output.read_bytes()
|
|
layout = read_layout(output)
|
|
self.assertEqual(before, output.read_bytes())
|
|
self.assertEqual(layout, result['previewLayout'])
|
|
sheet = layout['sheets'][0]
|
|
cells = {cell['reference']: cell for cell in sheet['cells']}
|
|
self.assertEqual(cells['C23']['text'], '测试收款人')
|
|
self.assertEqual(cells['C25']['text'], '001234567890')
|
|
self.assertEqual(cells['F27']['text'].strip(), '544.30')
|
|
self.assertEqual(cells['D29']['text'], '伍佰肆拾肆元叁角整')
|
|
self.assertNotIn('D23', cells)
|
|
self.assertAlmostEqual(cells['C23']['x'] + cells['C23']['width'], sheet['width'])
|
|
self.assertEqual(cells['H30']['style']['rotation'], 255)
|
|
self.assertGreater(cells['A30']['overflowRight'], cells['A30']['x'] + cells['A30']['width'])
|
|
self.assertLessEqual(cells['A30']['overflowRight'], cells['D30']['x'])
|
|
_, _, document = Template(output).sheet(sheet['name'])
|
|
row = document.find(tag('sheetData') + '/' + tag('row') + '[@r="30"]')
|
|
self.assertAlmostEqual(cells['A30']['height'], float(row.get('ht')) * 4 / 3)
|
|
self.assertAlmostEqual(cells['H30']['height'], sum(cells[ref]['height'] for ref in ['A30', 'A31', 'A32']))
|
|
self.assertEqual(cells['F27']['style']['horizontal'], 'center')
|
|
self.assertEqual(cells['F27']['style']['font'], 'SimSun-ExtB')
|
|
self.assertEqual(cells['C23']['style']['borders']['right']['style'], 'medium')
|
|
self.assertLess(sheet['width'], 1200)
|
|
self.assertGreater(sheet['height'], 1200)
|
|
if os.environ.get('RECEIPT_PREVIEW_FIXTURE_DIR'):
|
|
folder = Path(os.environ['RECEIPT_PREVIEW_FIXTURE_DIR'])
|
|
folder.mkdir(parents=True, exist_ok=True)
|
|
(folder / 'layout.json').write_text(json.dumps(layout, ensure_ascii=False), encoding='utf-8')
|
|
(folder / 'preview.xlsx').write_bytes(before)
|
|
|
|
def test_all_visible_sheets_and_pagination_remain_available(self):
|
|
template, mapping = self.fixture.alternate()
|
|
state = dict(matches=self.fixture.state['matches'] + [self.fixture.match('third', '住宿', '50')])
|
|
output, _ = self.fixture.export(mapping=mapping, template=template, state=state)
|
|
layout = prepare_preview(output)['previewLayout']
|
|
self.assertEqual([sheet['name'] for sheet in layout['sheets']], ['组 B 报销', '保留说明', '组 B 报销-续2'])
|
|
notes = layout['sheets'][1]
|
|
self.assertEqual(notes['cells'][0]['text'], '保持这个工作表不变')
|
|
self.assertTrue(all(sheet['width'] > 0 and sheet['height'] > 0 for sheet in layout['sheets']))
|
|
|
|
def test_hidden_rows_columns_and_cells_outside_print_area(self):
|
|
output, _ = self.fixture.export()
|
|
template = Template(output)
|
|
_, path, document = template.sheet('个人报销单')
|
|
document.find(tag('cols'))[0].set('hidden', '1')
|
|
row = document.find(tag('sheetData'))[0]
|
|
row.set('hidden', '1')
|
|
from expense_template import set_cell
|
|
set_cell(document, 'J36', '不能遗漏打印区域以外的内容')
|
|
template.parts[path] = encoded(document)
|
|
self.fixture.rewrite(output, template.parts)
|
|
sheet = read_layout(output)['sheets'][0]
|
|
cells = {cell['reference']: cell for cell in sheet['cells']}
|
|
self.assertNotIn('A9', cells)
|
|
self.assertNotIn('A1', cells)
|
|
self.assertEqual(cells['B9']['x'], 0)
|
|
self.assertIn('J36', cells)
|
|
|
|
def test_oversized_range_fails_instead_of_truncating(self):
|
|
output, _ = self.fixture.export()
|
|
template = Template(output)
|
|
_, path, document = template.sheet('个人报销单')
|
|
from expense_template import set_cell
|
|
set_cell(document, 'IV2000', 'too large')
|
|
template.parts[path] = encoded(document)
|
|
self.fixture.rewrite(output, template.parts)
|
|
with self.assertRaisesRegex(ValueError, '范围过大'):
|
|
read_layout(output)
|
|
|
|
def test_drawings_and_conditional_formatting_have_explicit_warning(self):
|
|
output, _ = self.fixture.export()
|
|
template = Template(output)
|
|
_, path, document = template.sheet('个人报销单')
|
|
ET.SubElement(document, tag('conditionalFormatting'), sqref='H9')
|
|
template.parts[path] = encoded(document)
|
|
self.fixture.rewrite(output, template.parts)
|
|
self.assertTrue(read_layout(output)['warnings'])
|
|
|
|
def test_number_formats_and_text_identity(self):
|
|
warnings = set()
|
|
for value, kind, code, expected in [
|
|
('2122.60', 'n', '#,##0.00', '2,122.60'),
|
|
('662.8', 'n', '0.00_ ', '662.80'),
|
|
('0', 'n', '#,##0.00;(#,##0.00);"-"', '-'),
|
|
('-25.3', 'n', '#,##0.00;(#,##0.00)', '(25.30)'),
|
|
('0.125', 'n', '0.0%', '12.5%'),
|
|
('8', 'n', '0000', '0008'),
|
|
('80', 'n', '"¥"#,##0.00', '¥80.00'),
|
|
('001234567890123456789', 'inlineStr', 'General', '001234567890123456789'),
|
|
('=NotAFormula', 'inlineStr', 'General', '=NotAFormula'),
|
|
('1', 'b', 'General', 'TRUE'),
|
|
('12.50', 'n', 'General', '12.5'),
|
|
('12.50', 'n', '0.##', '12.5'),
|
|
('12', 'n', '0.0#', '12.0'),
|
|
('1', 'n', 'mm-dd-yy', '01-01-00'),
|
|
]:
|
|
with self.subTest(value=value, code=code):
|
|
self.assertEqual(formatted(value, kind, code, False, warnings), expected)
|
|
self.assertEqual(formatted('1', 'n', 'yyyy/m/d', True, warnings), '1904/1/2')
|
|
self.assertFalse(warnings)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|