chore(小程序): 初始化工程仓库
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
import {test} from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import {cellAtPoint} from '../src/core/canvas.ts'
|
||||
test('页面滚动和画布偏移后仍准确定位格子',()=>{
|
||||
assert.equal(cellAtPoint(125,345,{left:100,top:300,width:100,height:100},10,10),42)
|
||||
assert.equal(cellAtPoint(125,45,{left:100,top:0,width:100,height:100},10,10),42)
|
||||
assert.equal(cellAtPoint(125,-1,{left:100,top:0,width:100,height:100},10,10),null)
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
import {test} from 'node:test'
|
||||
import {strict as assert} from 'node:assert'
|
||||
import {creationSize,readDraft} from '../src/core/creation.ts'
|
||||
test('制作尺寸拒绝小数、空值与超限',()=>{assert.equal(creationSize('29'),29);for(let value of ['', '8.5','97','7'])assert.throws(()=>creationSize(value))})
|
||||
test('草稿恢复允许空白画布并拒绝未知色号与损坏格数',()=>{let draft={title:'草稿',width:8,height:8,cells:Array(64).fill(null),colors:[]};assert.equal(readDraft(draft).cells.length,64);assert.throws(()=>readDraft({...draft,cells:['BAD',...Array(63).fill(null)]}));assert.throws(()=>readDraft({...draft,cells:[]}))})
|
||||
@@ -0,0 +1,5 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import {route,mainRoute,returnAction} from '../src/core/navigation.ts'
|
||||
test('分包参数编码,空参数省略',()=>{assert.equal(route('packages/patterns/detail',{id:'a?b&c',empty:undefined}),'/packages/patterns/detail?id=a%3Fb%26c');assert.equal(route('/packages/account/library',{type:'works'}),'/packages/account/library?type=works')})
|
||||
test('主包返回使用tab路由,无历史时不navigateBack',()=>{assert.equal(mainRoute('make'),'/pages/make/index');assert.deepEqual(returnAction(1,'store'),{type:'tab',url:'/pages/store/index'});assert.deepEqual(returnAction(2,'mine'),{type:'back'})})
|
||||
@@ -0,0 +1,39 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { EndpointRouter } from '../src/core/network.ts'
|
||||
|
||||
let local = 'https://lan.example.com'
|
||||
let remote = 'https://api.example.com'
|
||||
test('蜂窝网络与未知网络不探测内网', async () => {
|
||||
for (let type of ['4g', '5g', 'unknown']) {
|
||||
let probes = 0
|
||||
let r = new EndpointRouter({ local, remote, storeId: 's1' }, async () => { probes++; return 's1' })
|
||||
assert.equal(await r.select(type), remote)
|
||||
assert.equal(probes, 0)
|
||||
}
|
||||
})
|
||||
test('Wi-Fi 只有对应门店探测成功才走内网,失败进入冷却', async () => {
|
||||
let probes = 0
|
||||
let r = new EndpointRouter({ local, remote, storeId: 's1' }, async () => { probes++; return 's2' })
|
||||
assert.equal(await r.select('wifi'), remote)
|
||||
assert.equal(await r.select('wifi'), remote)
|
||||
assert.equal(probes, 1)
|
||||
let good = new EndpointRouter({ local, remote, storeId: 's1' }, async () => 's1')
|
||||
assert.equal(await good.select('wifi'), local)
|
||||
})
|
||||
test('网络切换后丢弃过期内网探测结果', async () => {
|
||||
let resolve!: (value: string) => void
|
||||
let r = new EndpointRouter({ local, remote, storeId: 's1' }, () => new Promise(res => { resolve = res }))
|
||||
let old = r.select('wifi')
|
||||
assert.equal(await r.select('4g'), remote)
|
||||
resolve('s1')
|
||||
assert.equal(await old, remote)
|
||||
assert.equal(r.current, remote)
|
||||
})
|
||||
test('无网络明确离线;连通失败切回公网', async () => {
|
||||
let r = new EndpointRouter({ local, remote, storeId: 's1' }, async () => 's1')
|
||||
await assert.rejects(r.select('none'), /网络/)
|
||||
await r.select('wifi')
|
||||
r.fallback()
|
||||
assert.equal(r.current, remote)
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { quantize, countBeads, validateGrid, cropRect } from '../src/core/pattern.ts'
|
||||
let colors = [{ code: 'B', name: '黑', hex: '#000000', location: '', available: true }, { code: 'W', name: '白', hex: '#ffffff', location: '', available: true }]
|
||||
test('透明像素不是白色豆;统计仅包括实际豆子', () => {
|
||||
let cells = quantize(new Uint8ClampedArray([255,255,255,255, 0,0,0,255, 255,255,255,0]), colors)
|
||||
assert.deepEqual(cells, ['W','B',null])
|
||||
assert.deepEqual(countBeads(cells), { W: 1, B: 1 })
|
||||
})
|
||||
test('色板量化使用最近的可用颜色', () => {
|
||||
assert.deepEqual(quantize(new Uint8ClampedArray([230,230,230,255]), colors), ['W'])
|
||||
assert.throws(() => quantize(new Uint8ClampedArray([0,0,0,255]), []), /色板/)
|
||||
})
|
||||
test('非法尺寸、未知色号、空图不能保存', () => {
|
||||
assert.throws(() => validateGrid(2, 2, ['W'], colors), /网格/)
|
||||
assert.throws(() => validateGrid(1, 1, ['X'], colors), /色号/)
|
||||
assert.throws(() => validateGrid(1, 1, [null], colors), /空/)
|
||||
assert.doesNotThrow(() => validateGrid(1, 1, ['W'], colors))
|
||||
})
|
||||
test('中心裁剪保持目标比例', () => {
|
||||
assert.deepEqual(cropRect(400,200,1), { x:100,y:0,width:200,height:200 })
|
||||
assert.deepEqual(cropRect(200,400,1), { x:0,y:100,width:200,height:200 })
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
import {test} from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import {BusinessError,requestWithFallback,submitAndRefresh} from '../src/core/transport.ts'
|
||||
test('内网写入结果不明时,公网重放保持相同请求标识与内容',async()=>{
|
||||
let body={requestId:'one-key',kind:'help'},calls:any[]=[]
|
||||
let task=await requestWithFallback('lan','lan','public','/service-requests','POST',body,async(base:string,_path:string,_method:string,data:any)=>{calls.push({base,data});if(base==='lan')throw new Error('timeout');return{id:'same-task'}},()=>{})
|
||||
assert.deepEqual(task,{id:'same-task'});assert.equal(calls.length,2);assert.equal(calls[0].data,calls[1].data)
|
||||
})
|
||||
test('业务错误不切换、不重放;无幂等键的写入不自动重试',async()=>{
|
||||
let attempts=0
|
||||
await assert.rejects(requestWithFallback('lan','lan','public','/patterns','POST',{},async()=>{attempts++;throw new BusinessError('无权限',401)},()=>{}),/无权限/)
|
||||
assert.equal(attempts,1)
|
||||
await assert.rejects(requestWithFallback('lan','lan','public','/patterns','POST',{},async()=>{attempts++;throw new Error('timeout')},()=>{}),/确认记录/)
|
||||
assert.equal(attempts,2)
|
||||
})
|
||||
test('提交成功但刷新失败仍保留已提交任务,不误报提交失败',async()=>{
|
||||
let committed:any=null
|
||||
let result=await submitAndRefresh(async()=>({id:'task-1'}),(t:any)=>{committed=t},async()=>{throw new Error('列表暂不可用')})
|
||||
assert.equal(committed.id,'task-1');assert.equal(result.task.id,'task-1');assert.match(result.refreshError,/列表暂不可用/)
|
||||
})
|
||||
@@ -0,0 +1,75 @@
|
||||
import {test} from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import {build} from 'esbuild'
|
||||
import {fileURLToPath} from 'node:url'
|
||||
|
||||
// api.ts 依赖 Vite 环境;只隔离环境适配层,所有工作流逻辑使用实际源代码与 Vue refs。
|
||||
let bundle = await build({
|
||||
stdin:{contents:"import * as workflow from './src/stores/visit.ts'; export {workflow}; export {reactive} from 'vue'",resolveDir:fileURLToPath(new URL('..',import.meta.url))},
|
||||
bundle:true,platform:'node',format:'esm',write:false,
|
||||
plugins:[{name:'isolate-vite-api',setup(builder){
|
||||
builder.onResolve({filter:/services\/api$/},()=>({path:'vite-api',namespace:'test'}))
|
||||
builder.onLoad({filter:/.*/,namespace:'test'},()=>({contents:"export const storeId='test-store';export function api(){throw new Error('Inject test API')}",loader:'js'}))
|
||||
}}]
|
||||
})
|
||||
let module = await import('data:text/javascript;base64,'+Buffer.from(bundle.outputFiles[0].text).toString('base64'))
|
||||
function fixture(){
|
||||
assert.equal(typeof module.workflow.createVisitWorkflow,'function','visit workflow must expose isolated API dependencies')
|
||||
let identity='user-a', failPost=false, failRefresh=false, posted:any[]=[], current:any=null
|
||||
let send=async(path:string,method='GET',body?:any)=>{
|
||||
if(path==='/visits/current'&&method==='DELETE'){current=null;return null}
|
||||
if(path==='/visits/current')return current
|
||||
if(path==='/visits'&&method==='POST')return current={id:'visit-'+body.tableCode,storeId:'test-store',tableCode:body.tableCode,status:'active'}
|
||||
if(path==='/service-requests'&&method==='POST'){posted.push({...body});if(failPost)throw new Error('ambiguous network');return {id:'task-'+posted.length,userId:identity,tableCode:current.tableCode,storeId:'test-store',status:'pending',...body}}
|
||||
if(failRefresh)throw new Error('refresh offline')
|
||||
return []
|
||||
}
|
||||
let state=module.reactive(module.workflow.createVisitWorkflow({storeId:'test-store',identity:()=>identity,ensure:async()=>{},api:(...args:any[])=>send(args[0],args[1],args[2])}))
|
||||
return {state,posted,getIdentity:()=>identity,setIdentity:(v:string)=>{identity=v},setCurrent:(v:any)=>{current=v},setFailPost:(v:boolean)=>{failPost=v},setFailRefresh:(v:boolean)=>{failRefresh=v},setAPI:(fn:typeof send)=>{send=fn}}
|
||||
}
|
||||
test('模糊失败重试复用UUID;刷新失败后保留成功且禁止再次提交',{timeout:3000},async()=>{
|
||||
let f=fixture(),s=f.state;await s.confirmVisit('T01');s.note='same';f.setFailPost(true)
|
||||
await assert.rejects(s.submit(),/ambiguous/);f.setFailPost(false);f.setFailRefresh(true)
|
||||
let result=await s.submit();assert.deepEqual(f.posted[0],f.posted[1]);assert.match(f.posted[0].requestId,/^[0-9a-f-]{36}$/)
|
||||
assert.match(result.refreshError,/offline/);assert.ok(s.submitted);await s.submit();assert.equal(f.posted.length,2)
|
||||
s.startNewRequest();await s.submit();assert.notEqual(f.posted[1].requestId,f.posted[2].requestId)
|
||||
})
|
||||
test('首入申请去确认到店保留草稿;新到店会话清除旧结果与幂等上下文',{timeout:3000},async()=>{
|
||||
let f=fixture(),s=f.state;s.configureRequest({kind:'print',patternId:'pattern-1'});s.note='keep draft'
|
||||
await s.confirmVisit('T01');assert.equal(s.kind,'print');assert.equal(s.note,'keep draft');assert.equal(s.patternId,'pattern-1')
|
||||
await s.submit();let id=f.posted[0].requestId;await s.leave();assert.equal(s.submitted,null)
|
||||
await s.confirmVisit('T02');assert.equal(s.submitted,null);await s.submit();assert.equal(f.posted.length,2);assert.equal(s.submitted.tableCode,'T02');assert.notEqual(f.posted[1].requestId,id)
|
||||
})
|
||||
test('服务端刷新发现新的visit,也清除旧成功结果',{timeout:3000},async()=>{
|
||||
let f=fixture(),s=f.state;await s.confirmVisit('T01');await s.submit()
|
||||
f.setCurrent({id:'new-visit',storeId:'test-store',tableCode:'T02',status:'active'});await s.refreshVisit();assert.equal(s.submitted,null);assert.equal(s.visit.tableCode,'T02')
|
||||
})
|
||||
test('换账号清除旧结果/草稿/记录,旧账号在途读取不能回写',{timeout:3000},async()=>{
|
||||
let f=fixture(),s=f.state;await s.confirmVisit('T01');s.note='private';await s.submit()
|
||||
let resolve:(v:any)=>void=()=>{};f.setAPI(async()=>new Promise(r=>{resolve=r}))
|
||||
let read=s.refreshTasks();f.setIdentity('user-b');s.configureRequest({})
|
||||
assert.equal(s.submitted,null);assert.equal(s.visit,null);assert.equal(s.note,'');assert.equal(s.tasks.length,0)
|
||||
resolve([{id:'old-user-task'}]);await read;assert.equal(s.tasks.length,0)
|
||||
})
|
||||
test('在途提交期间换账号不写回旧成功结果',{timeout:3000},async()=>{
|
||||
let f=fixture(),s=f.state;await s.confirmVisit('T01')
|
||||
let resolve:(v:any)=>void=()=>{};f.setAPI(async()=>new Promise(r=>{resolve=r}))
|
||||
let posting=s.submit();f.setIdentity('user-b');s.configureRequest({})
|
||||
resolve({id:'old-task',tableCode:'T01'});await assert.rejects(posting,/账号|会话/);assert.equal(s.submitted,null);assert.equal(s.tasks.length,0)
|
||||
})
|
||||
test('首次登录确认桌位保留草稿;登出清除成功与旧记录',{timeout:3000},async()=>{
|
||||
let f=fixture(),s=f.state;f.setIdentity('');s.configureRequest({kind:'ironing'});s.note='first draft'
|
||||
// initial logged-out state is the normal request → visit journey.
|
||||
let initial=module.reactive(module.workflow.createVisitWorkflow({storeId:'test-store',identity:f.getIdentity,ensure:async()=>f.setIdentity('user-a'),api:async(path:string,_method:string,body:any)=>({id:'first',storeId:'test-store',tableCode:body.tableCode,status:'active'})}))
|
||||
initial.configureRequest({kind:'ironing'});initial.note='first draft';await initial.confirmVisit('T01');assert.equal(initial.kind,'ironing');assert.equal(initial.note,'first draft')
|
||||
f.setIdentity('user-a');await s.confirmVisit('T01');await s.submit();f.setIdentity('');await s.refreshVisit();assert.equal(s.submitted,null);assert.equal(s.visit,null);assert.equal(s.tasks.length,0)
|
||||
})
|
||||
|
||||
test('换账号后旧到店读取不回写;未关联打印不发请求',{timeout:3000},async()=>{
|
||||
let f=fixture(),s=f.state;await s.confirmVisit('T01');s.configureRequest({kind:'print'})
|
||||
await assert.rejects(s.submit(),/图纸/);assert.equal(f.posted.length,0)
|
||||
let resolve:(v:any)=>void=()=>{};f.setAPI(async()=>new Promise(r=>{resolve=r}))
|
||||
let read=s.refreshVisit();f.setIdentity('user-b');s.configureRequest({})
|
||||
resolve({id:'old-visit',storeId:'test-store',tableCode:'T01',status:'active'});await read
|
||||
assert.equal(s.visit,null);assert.equal(s.submitted,null)
|
||||
})
|
||||
Reference in New Issue
Block a user