221 lines
12 KiB
Python
221 lines
12 KiB
Python
"""Execute sampling list/save JavaScript with a small DOM and fetch substitute."""
|
||
import json
|
||
import unittest
|
||
from pathlib import Path
|
||
|
||
try:
|
||
import quickjs
|
||
except ImportError:
|
||
quickjs = None
|
||
|
||
|
||
SOURCE = (Path(__file__).resolve().parents[1] / 'static/js/app.js').read_text()
|
||
|
||
|
||
@unittest.skipIf(quickjs is None, 'QuickJS is required for frontend execution')
|
||
class SpotApplicationFrontendTest(unittest.TestCase):
|
||
def setUp(self):
|
||
self.ctx = quickjs.Context()
|
||
self.ctx.eval('''
|
||
var window = globalThis, nodes = {}, requests = [], alerts = [];
|
||
var _spotApplicationRowsCache = {};
|
||
function node() {
|
||
return {value:'', innerHTML:'', style:{}, listeners:{},
|
||
setCustomValidity:function(message){this.error=message;},
|
||
classList:{toggle:function(){}},
|
||
remove:function(){this.removed=true;},
|
||
querySelector:function(){return document.getElementById('submitButton');},
|
||
addEventListener:function(name, fn){this.listeners[name]=fn;}};
|
||
}
|
||
var document = {
|
||
getElementById:function(id){return nodes[id] || (nodes[id]=node());},
|
||
querySelectorAll:function(){return [];},
|
||
createElement:node,
|
||
body:{appendChild:function(el){nodes[el.id]=el;}}
|
||
};
|
||
window._hasPagePerm = function(){return true;};
|
||
var FormData = function(){};
|
||
var alert = function(message){alerts.push(message);};
|
||
var fields = {};
|
||
['product_name','barcode','sample_quantity','recipient_info','brand','group_name','department'].forEach(function(name){fields[name]=node();});
|
||
var form = document.getElementById('spotApplicationForm');
|
||
form.elements = {namedItem:function(name){return fields[name];}};
|
||
form.checkValidity = function(){return ['product_name','barcode','sample_quantity','recipient_info'].every(function(name){
|
||
return fields[name].value.trim() && !fields[name].error;
|
||
}) && Object.keys(fields).every(function(name){return !fields[name].error;});};
|
||
form.reportValidity = function(){};
|
||
var listResponse = {code:0,data:{rows:[{
|
||
id:7,product_name:'刚保存的商品',barcode:'00123',sample_quantity:2,
|
||
status:'待处理',image_path:'/static/uploads/spot_application/test.png'
|
||
}],total:1,size:20,page:1}};
|
||
var fetch = function(url) {
|
||
requests.push(url);
|
||
return Promise.resolve({json:function(){
|
||
return Promise.resolve(url.indexOf('/create') >= 0 ?
|
||
{code:0,id:7} : listResponse);
|
||
}});
|
||
};
|
||
''')
|
||
self.ctx.eval(SOURCE[SOURCE.index('function escJs('):SOURCE.index('// 成本价、单价')])
|
||
self.ctx.eval(SOURCE[SOURCE.index('window.switchSpotApplicationTab='):
|
||
SOURCE.index('function decideSpotApplication(')])
|
||
|
||
def execute(self, code):
|
||
self.ctx.eval(code)
|
||
while self.ctx.execute_pending_job():
|
||
pass
|
||
|
||
def test_successful_list_is_visible_in_every_tab(self):
|
||
for tab in ('application', 'arrived', 'rejected'):
|
||
self.execute('window.switchSpotApplicationTab(' + json.dumps(tab) + ')')
|
||
html = self.ctx.eval('nodes.spotAppRows.innerHTML')
|
||
self.assertIn('刚保存的商品', html)
|
||
self.assertNotIn('网络异常', html)
|
||
self.assertEqual(self.ctx.eval('nodes.spotAppTotal.textContent'), '共 1 条')
|
||
|
||
def test_empty_list_remains_empty_instead_of_network_error(self):
|
||
self.execute('listResponse.data.rows=[];listResponse.data.total=0;loadSpotApplicationList()')
|
||
self.assertIn('暂无记录', self.ctx.eval('nodes.spotAppRows.innerHTML'))
|
||
self.assertEqual(self.ctx.eval('nodes.spotAppPagination.innerHTML'), '')
|
||
|
||
def test_pagination_changes_request_page_and_keeps_search_and_tab(self):
|
||
self.execute('''
|
||
nodes.spotAppSearch=document.getElementById('spotAppSearch');
|
||
nodes.spotAppSearch.value='品牌';
|
||
window._spotAppTab='rejected';
|
||
renderSpotApplicationPagination(45,20,1);
|
||
''')
|
||
html = self.ctx.eval('nodes.spotAppPagination.innerHTML')
|
||
self.assertIn('disabled', html)
|
||
self.assertIn('window._spotAppPage=2;loadSpotApplicationList()', html)
|
||
self.execute('window._spotAppPage=2;loadSpotApplicationList()')
|
||
url = self.ctx.eval('requests[requests.length-1]')
|
||
self.assertIn('page=2', url)
|
||
self.assertIn('tab=rejected', url)
|
||
self.assertIn('search=%E5%93%81%E7%89%8C', url)
|
||
self.execute('renderSpotApplicationPagination(45,20,3)')
|
||
self.assertNotIn('window._spotAppPage=4', self.ctx.eval('nodes.spotAppPagination.innerHTML'))
|
||
|
||
def test_create_success_resets_filters_and_shows_saved_row(self):
|
||
self.execute('''
|
||
document.getElementById('spotAppSearch').value='不匹配的旧搜索';
|
||
window._spotAppPage=4;window._spotAppTab='rejected';
|
||
openSpotApplicationModal();
|
||
fields.product_name.value='刚保存的商品';fields.barcode.value='0012345678901';
|
||
fields.sample_quantity.value='2';fields.recipient_info.value='收件信息';
|
||
nodes.spotApplicationForm.listeners.input.call(nodes.spotApplicationForm);
|
||
nodes.spotApplicationForm.listeners.submit.call(
|
||
nodes.spotApplicationForm,{preventDefault:function(){}});
|
||
''')
|
||
self.assertTrue(self.ctx.eval('nodes.spotApplicationModal.removed'))
|
||
self.assertEqual(self.ctx.eval('nodes.spotAppSearch.value'), '')
|
||
self.assertEqual(self.ctx.eval('window._spotAppPage'), 1)
|
||
self.assertEqual(self.ctx.eval('window._spotAppTab'), 'application')
|
||
self.assertIn('刚保存的商品', self.ctx.eval('nodes.spotAppRows.innerHTML'))
|
||
self.assertEqual(json.loads(self.ctx.eval('JSON.stringify(alerts)')), ['申请已添加'])
|
||
|
||
def test_recipient_templates_lock_fields_and_preserve_guangzhou_edits(self):
|
||
self.execute('''
|
||
['recipient_template','recipient_address','recipient_name','recipient_phone'].forEach(
|
||
function(name){fields[name]=node();});
|
||
openSpotApplicationModal();
|
||
fields.product_name.value='样品';fields.barcode.value='0012345678901';
|
||
fields.sample_quantity.value='2';
|
||
fields.recipient_template.value='shenzhen';
|
||
fields.recipient_template.listeners.change();
|
||
''')
|
||
self.assertFalse(self.ctx.eval('nodes.submitButton.disabled'))
|
||
self.assertTrue(self.ctx.eval('fields.recipient_address.readOnly && fields.recipient_name.readOnly && fields.recipient_phone.readOnly'))
|
||
self.assertIn('收件人:一部样品\n电话:17771977816', self.ctx.eval('fields.recipient_info.value'))
|
||
self.execute('''
|
||
fields.recipient_template.value='guangzhou';
|
||
fields.recipient_template.listeners.change();
|
||
''')
|
||
self.assertTrue(self.ctx.eval('fields.recipient_address.readOnly'))
|
||
self.assertFalse(self.ctx.eval('fields.recipient_name.readOnly || fields.recipient_phone.readOnly'))
|
||
self.assertEqual(self.ctx.eval('fields.recipient_name.value'), 'XXX')
|
||
self.assertEqual(self.ctx.eval('fields.recipient_phone.value'), '111111111111')
|
||
self.execute('''
|
||
fields.recipient_name.value='张三';fields.recipient_phone.value='13800138000';
|
||
nodes.spotApplicationForm.listeners.input.call(nodes.spotApplicationForm);
|
||
''')
|
||
self.assertIn('收件人:张三\n电话:13800138000', self.ctx.eval('fields.recipient_info.value'))
|
||
self.execute('''
|
||
fields.recipient_phone.value=' ';
|
||
nodes.spotApplicationForm.listeners.input.call(nodes.spotApplicationForm);
|
||
''')
|
||
self.assertTrue(self.ctx.eval('nodes.submitButton.disabled'))
|
||
self.execute('''
|
||
fields.recipient_template.value='shenzhen';fields.recipient_template.listeners.change();
|
||
''')
|
||
self.assertFalse(self.ctx.eval('nodes.submitButton.disabled'))
|
||
self.assertEqual(self.ctx.eval('fields.recipient_name.value'), '一部样品')
|
||
self.execute('''
|
||
fields.recipient_template.value='';fields.recipient_template.listeners.change();
|
||
''')
|
||
self.assertTrue(self.ctx.eval('nodes.submitButton.disabled'))
|
||
|
||
def test_brand_uses_fixed_select_options(self):
|
||
self.execute('openSpotApplicationModal()')
|
||
html = self.ctx.eval('nodes.spotApplicationModal.innerHTML')
|
||
self.assertIn('<select name="brand">', html)
|
||
self.assertIn('<select name="department">', html)
|
||
self.assertNotIn('<option value="阿里">', html)
|
||
for name in ('泊紫汀兰', '洁维塔'):
|
||
self.assertIn(f'<option value="{name}">{name}</option>', html)
|
||
|
||
def test_brand_changes_update_group_requirement_and_save_button(self):
|
||
self.execute('''
|
||
openSpotApplicationModal();
|
||
fields.product_name.value='样品';fields.barcode.value='0012345678901';
|
||
fields.sample_quantity.value='2';fields.recipient_info.value='收件信息';
|
||
fields.brand.value='洁维塔';
|
||
nodes.spotApplicationForm.listeners.change.call(nodes.spotApplicationForm);
|
||
''')
|
||
self.assertTrue(self.ctx.eval('fields.group_name.required && nodes.submitButton.disabled'))
|
||
self.assertEqual(self.ctx.eval('nodes.spotApplicationGroupRequired.style.display'), '')
|
||
self.execute('''
|
||
fields.group_name.value='京东1组';
|
||
nodes.spotApplicationForm.listeners.change.call(nodes.spotApplicationForm);
|
||
''')
|
||
self.assertFalse(self.ctx.eval('nodes.submitButton.disabled'))
|
||
self.execute('''
|
||
fields.group_name.value='';fields.brand.value='泊紫汀兰';
|
||
nodes.spotApplicationForm.listeners.change.call(nodes.spotApplicationForm);
|
||
''')
|
||
self.assertFalse(self.ctx.eval('fields.group_name.required || nodes.submitButton.disabled'))
|
||
self.assertEqual(self.ctx.eval('fields.group_name.error'), '')
|
||
self.assertEqual(self.ctx.eval('nodes.spotApplicationGroupRequired.style.display'), 'none')
|
||
|
||
def test_result_headers_and_rows_follow_requested_order(self):
|
||
import re
|
||
for tab, expected in (
|
||
('arrived', ['序号','品牌','产品名称','图片','条形码','平台','运营','到货时间']),
|
||
('rejected', ['序号','品牌','产品名称','平台','运营','是否驳回','驳回原因']),
|
||
):
|
||
self.execute('renderSpotApplicationHead(' + json.dumps(tab) + ')')
|
||
self.assertEqual(re.findall(r'<th>(.*?)</th>', self.ctx.eval('nodes.spotAppHead.innerHTML')), expected)
|
||
self.execute('''
|
||
window._spotAppTab=''' + json.dumps(tab) + ''';
|
||
listResponse.data.page=2;
|
||
Object.assign(listResponse.data.rows[0],{
|
||
brand:'测试品牌', image_path:'/static/uploads/spot_application/test.png',
|
||
platform:'京东', operator:'测试运营', arrival_time:'2026-10-15',
|
||
rejected:'是', reject_reason:'申请驳回原因'
|
||
});
|
||
loadSpotApplicationList();
|
||
''')
|
||
html = self.ctx.eval('nodes.spotAppRows.innerHTML')
|
||
cells = re.findall(r'<td[^>]*>(.*?)</td>', html)
|
||
self.assertEqual(len(cells), len(expected))
|
||
self.assertEqual(cells[:3], ['21','测试品牌','刚保存的商品'])
|
||
if tab == 'arrived':
|
||
self.assertIn('<img', cells[3])
|
||
self.assertEqual(cells[4:], ['00123','京东','测试运营','2026-10-15'])
|
||
else:
|
||
self.assertEqual(cells[3:], ['京东','测试运营','是','申请驳回原因'])
|
||
|
||
|
||
if __name__ == '__main__':
|
||
unittest.main()
|