"""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('', html) self.assertNotIn('', 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'(.*?)', 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']*>(.*?)', html) self.assertEqual(len(cells), len(expected)) self.assertEqual(cells[:3], ['21','测试品牌','刚保存的商品']) if tab == 'arrived': self.assertIn('