1
This commit is contained in:
@@ -1,16 +1,15 @@
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import subprocess
|
||||
import sys
|
||||
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_template import Template, encoded, export_custom_expense, inspect_template, set_cell, tag
|
||||
from expense_preview import prepare_preview
|
||||
from expense_template import Template, encoded, tag
|
||||
from spreadsheet_layout import formatted, read_layout
|
||||
|
||||
|
||||
@@ -20,93 +19,143 @@ class SpreadsheetLayoutTests(unittest.TestCase):
|
||||
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'))
|
||||
def test_wps_signature_and_accounting_regressions(self):
|
||||
source = Path(os.environ.get('RECEIPT_PREVIEW_SOURCE', self.fixture.template))
|
||||
original = source.read_bytes()
|
||||
mapping = inspect_template(source)['sheets'][0]['mapping']
|
||||
output = self.fixture.root / 'preview.xlsx'
|
||||
export_custom_expense(dict(matches=[self.fixture.match('changed', '交通', '447')]),
|
||||
output, source, mapping, {},
|
||||
['部门长', '剧组出纳', '制片主任', '剧组会计', '执行制片人'],
|
||||
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(output.read_bytes(), before)
|
||||
self.assertEqual(source.read_bytes(), original)
|
||||
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.assertNotIn('templatePreviewLayout', result)
|
||||
cells = {c['reference']: c for c in layout['sheets'][0]['cells']}
|
||||
for ref, label in [('A30', '部门长'), ('D30', '剧组出纳'), ('A31', '制片主任'), ('D31', '剧组会计')]:
|
||||
self.assertEqual(cells[ref]['text'], label + ':')
|
||||
self.assertGreater(cells[ref]['overflowRight'], cells[ref]['x'] + cells[ref]['width'])
|
||||
self.assertEqual(cells['A30']['overflowRight'], cells['D30']['x'])
|
||||
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)
|
||||
self.assertEqual(cells['H30']['style']['vertical'], 'bottom')
|
||||
self.assertEqual(cells['H30']['style']['borders']['right']['style'], 'medium')
|
||||
self.assertEqual(cells['H30']['style']['borders']['bottom']['style'], 'medium')
|
||||
for ref in ['F27', 'H29']:
|
||||
self.assertTrue(cells[ref]['style']['accounting'])
|
||||
self.assertEqual(cells[ref]['text'].strip(), '447.00')
|
||||
self.assertTrue(cells[ref]['style']['bold'])
|
||||
self.assertNotIn('left', cells['D31']['style']['borders'])
|
||||
self.assertEqual(cells['C25']['text'], '001234567890')
|
||||
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_overflow_stops_at_text_and_merged_cells(self):
|
||||
output, _ = self.fixture.export()
|
||||
template = Template(output)
|
||||
_, path, document = template.sheet('个人报销单')
|
||||
set_cell(document, 'B30', '阻止覆盖')
|
||||
template.parts[path] = encoded(document)
|
||||
self.fixture.rewrite(output, template.parts)
|
||||
cells = {c['reference']: c for c in read_layout(output)['sheets'][0]['cells']}
|
||||
self.assertEqual(cells['A30']['overflowRight'], cells['A30']['x'] + cells['A30']['width'])
|
||||
self.assertEqual(cells['D30']['overflowRight'], cells['H30']['x'])
|
||||
self.assertEqual(cells['A32']['overflowRight'], cells['A32']['x'] + cells['A32']['width'])
|
||||
|
||||
def test_hidden_rows_columns_and_cells_outside_print_area(self):
|
||||
def test_visible_sheets_and_pagination(self):
|
||||
source, mapping = self.fixture.alternate()
|
||||
state = dict(matches=self.fixture.state['matches'] + [self.fixture.match('extra', '住宿', '50')])
|
||||
output, _ = self.fixture.export(mapping=mapping, template=source, state=state)
|
||||
layout = prepare_preview(output)['previewLayout']
|
||||
self.assertEqual([s['name'] for s in layout['sheets']], ['组 B 报销', '保留说明', '组 B 报销-续2'])
|
||||
|
||||
def test_unrelated_template_style_and_geometry(self):
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
|
||||
workbook = Workbook()
|
||||
sheet = workbook.active
|
||||
sheet.title = '横向费用单'
|
||||
sheet.column_dimensions['A'].width = 4
|
||||
sheet.column_dimensions['B'].width = 24
|
||||
sheet.column_dimensions['C'].width = 12
|
||||
sheet.column_dimensions['D'].width = 20
|
||||
sheet.column_dimensions['E'].width = 16
|
||||
sheet.column_dimensions['F'].width = 10
|
||||
sheet.row_dimensions[2].height = 42
|
||||
sheet.row_dimensions[6].height = 34
|
||||
sheet.merge_cells('B2:F2')
|
||||
sheet['B2'] = '另一种费用模板'
|
||||
sheet['B2'].font = Font(name='Arial', size=18, bold=True, color='145A32')
|
||||
sheet['B2'].fill = PatternFill('solid', fgColor='D5F5E3')
|
||||
sheet['B2'].alignment = Alignment(horizontal='center', vertical='center')
|
||||
sheet['B2'].border = Border(bottom=Side(style='double', color='145A32'))
|
||||
for ref, text in [('B5', '说明'), ('D5', '金额'), ('E5', '比例'), ('F5', '编号')]:
|
||||
sheet[ref] = text
|
||||
sheet.merge_cells('B6:C6')
|
||||
sheet['B6'] = '这里是长一些的费用说明,需要按模板换行'
|
||||
sheet['B6'].alignment = Alignment(wrap_text=True, vertical='top')
|
||||
sheet['D6'] = 1234.5
|
||||
sheet['D6'].number_format = '#,##0.00'
|
||||
sheet['D6'].alignment = Alignment(horizontal='right')
|
||||
sheet['E6'] = .125
|
||||
sheet['E6'].number_format = '0.0%'
|
||||
sheet['F6'] = '000012'
|
||||
sheet.print_area = 'A1:F8'
|
||||
workbook.create_sheet('说明页')['A1'] = '附加工作表'
|
||||
output = self.fixture.root / 'different-style.xlsx'
|
||||
workbook.save(output)
|
||||
before = output.read_bytes()
|
||||
layout = read_layout(output)
|
||||
self.assertEqual(output.read_bytes(), before)
|
||||
self.assertEqual([s['name'] for s in layout['sheets']], ['横向费用单', '说明页'])
|
||||
cells = {c['reference']: c for c in layout['sheets'][0]['cells']}
|
||||
self.assertNotIn('C2', cells)
|
||||
self.assertNotIn('C6', cells)
|
||||
self.assertEqual(cells['B2']['height'], 56)
|
||||
self.assertEqual(cells['B2']['style']['font'], 'Arial')
|
||||
self.assertEqual(cells['B2']['style']['fill'], 'D5F5E3')
|
||||
self.assertEqual(cells['B2']['style']['borders']['bottom']['style'], 'double')
|
||||
self.assertTrue(cells['B6']['style']['wrap'])
|
||||
self.assertEqual(cells['D6']['text'], '1,234.50')
|
||||
self.assertEqual(cells['D6']['style']['horizontal'], 'right')
|
||||
self.assertEqual(cells['E6']['text'], '12.5%')
|
||||
self.assertEqual(cells['F6']['text'], '000012')
|
||||
if os.environ.get('RECEIPT_PREVIEW_FIXTURE_DIR'):
|
||||
folder = Path(os.environ['RECEIPT_PREVIEW_FIXTURE_DIR'])
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
(folder / 'alternate-layout.json').write_text(json.dumps(layout, ensure_ascii=False), encoding='utf-8')
|
||||
|
||||
def test_hidden_dimensions_and_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', '不能遗漏打印区域以外的内容')
|
||||
document.find(tag('sheetData'))[0].set('hidden', '1')
|
||||
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']}
|
||||
cells = {c['reference']: c for c in read_layout(output)['sheets'][0]['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):
|
||||
def test_large_range_rejected(self):
|
||||
output, _ = self.fixture.export()
|
||||
template = Template(output)
|
||||
_, path, document = template.sheet('个人报销单')
|
||||
from expense_template import set_cell
|
||||
set_cell(document, 'IV2000', 'too large')
|
||||
set_cell(document, 'IV2000', 'range')
|
||||
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):
|
||||
def test_advanced_features_warn(self):
|
||||
output, _ = self.fixture.export()
|
||||
template = Template(output)
|
||||
_, path, document = template.sheet('个人报销单')
|
||||
@@ -115,29 +164,29 @@ import spreadsheet_layout
|
||||
self.fixture.rewrite(output, template.parts)
|
||||
self.assertTrue(read_layout(output)['warnings'])
|
||||
|
||||
def test_number_formats_and_text_identity(self):
|
||||
def test_numeric_and_date_formatting(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)
|
||||
for value, code, expected in [('447', '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)', '447.00'),
|
||||
('2122.6', '#,##0.00', '2,122.60'), ('-25.3', '0.00;(0.00)', '(25.30)'),
|
||||
('12.50', '0.##', '12.5'), ('12', '0.0#', '12.0'),
|
||||
('8', '0000', '0008'), ('0.125', '0.0%', '12.5%')]:
|
||||
self.assertEqual(formatted(value, 'n', code, False, warnings).strip(), expected)
|
||||
self.assertEqual(formatted('1', 'n', 'yyyy/m/d', True, warnings), '1904/1/2')
|
||||
self.assertFalse(warnings)
|
||||
|
||||
def test_sandbox_import(self):
|
||||
script = """
|
||||
import sys
|
||||
def audit(event, args):
|
||||
if event == 'open' and str(args[0]).endswith('mime.types'):
|
||||
raise PermissionError('System MIME database is unavailable')
|
||||
sys.addaudithook(audit)
|
||||
import spreadsheet_layout
|
||||
"""
|
||||
result = subprocess.run([sys.executable, '-c', script], cwd=Path(__file__).resolve().parents[1],
|
||||
capture_output=True, text=True)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user