Initial commit: Receipt Studio desktop application

This commit is contained in:
fuenmao
2026-10-19 11:09:09 +08:00
commit 2caac3c31f
77 changed files with 9846 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
import {_electron as electron,expect} from '@playwright/test'
import electronPath from 'electron'
import {build} from 'esbuild'
import {createRequire} from 'node:module'
import fs from 'node:fs/promises'
import path from 'node:path'
const root=process.cwd(),qa=path.join(root,'.qa','history-pagination')
await fs.mkdir(qa,{recursive:true})
const run=await fs.mkdtemp(path.join(qa,'run-')),profile=path.join(run,'profile'),output=path.join(run,'output'),runtime=path.join(run,'fixture.cjs')
await fs.mkdir(profile);await fs.mkdir(output)
await build({stdin:{contents:"export {Ledger} from './src/main/ledger';export {defaultSettings} from './src/shared/settings'",resolveDir:root},outfile:runtime,bundle:true,platform:'node',format:'cjs',logLevel:'silent'})
const {Ledger,defaultSettings}=createRequire(import.meta.url)(runtime)
const settings={...defaultSettings(output),numberPrefix:'PAGE'}
await fs.writeFile(path.join(profile,'settings.json'),JSON.stringify(settings))
const ledger=new Ledger(path.join(profile,'receipts.sqlite'));await ledger.open()
for(let i=1;i<=13;i++){
const school=i%2?'分页甲校':'分页乙校',studentName='学生'+String(i).padStart(2,'0')
const batch=await ledger.reserve({name:'分页测试'+String(i).padStart(2,'0')+'.xlsx',hash:String(i)},[{date:'2026-10-19',school,grade:'高一',studentName,customer:school+' 高一 '+studentName,item:'服务费',amountCents:10000,cashier:'',reviewer:''}],settings,null)
batch.status='completed';batch.outputDirectory=output;await ledger.update(batch)
}
const env={...process.env};delete env.ELECTRON_RUN_AS_NODE;delete env.ELECTRON_RENDERER_URL
let desktop
const errors=[],checks=[]
async function launch(){
desktop=await electron.launch({executablePath:electronPath,args:['.','--test-mode','--user-data-dir='+profile],cwd:root,env,timeout:60000})
const page=await desktop.firstWindow();page.setDefaultTimeout(20000);page.on('pageerror',error=>errors.push(error.message))
await page.getByText('把表格交给收据工坊').waitFor()
await page.getByRole('button',{name:/^生成记录/}).click()
return page
}
try{
let page=await launch()
const rows=()=>page.locator('.history-table tbody tr')
const position=()=>page.locator('.history-page-position')
async function idle(){
await expect(page.locator('[data-region="history"]')).not.toHaveAttribute('aria-busy','true')
await expect(page.locator('.history-search>span')).not.toContainText('正在')
}
await expect(rows()).toHaveCount(5);await idle()
await expect(page.getByLabel('生成记录每页条数')).toHaveValue('5')
await expect(position()).toHaveText('第 1 / 3 页')
await expect(page.locator('.history-range')).toContainText('共 13 条')
await expect(page.getByRole('button',{name:'生成记录上一页',exact:true})).toBeDisabled()
await expect(rows().first()).toContainText('分页测试13.xlsx')
await page.screenshot({path:path.join(qa,'pagination.png'),fullPage:true})
await page.getByRole('button',{name:'生成记录下一页',exact:true}).click();await idle()
await expect(position()).toHaveText('第 2 / 3 页');await expect(rows()).toHaveCount(5)
await expect(rows().first()).toContainText('分页测试08.xlsx')
await expect(page.locator('.history-range')).toContainText('第 6–10 条')
await page.getByRole('button',{name:'生成记录末页',exact:true}).click();await idle()
await expect(rows()).toHaveCount(3);await expect(position()).toHaveText('第 3 / 3 页')
await expect(page.getByRole('button',{name:'生成记录下一页',exact:true})).toBeDisabled()
checks.push('Default five rows, accurate total/range, next/last page and disabled boundaries')
await page.getByLabel('生成记录每页条数').selectOption('10');await idle()
await expect(rows()).toHaveCount(10);await expect(position()).toHaveText('第 1 / 2 页')
await page.getByLabel('生成记录每页条数').selectOption('20');await idle()
await expect(rows()).toHaveCount(13);await expect(position()).toHaveText('第 1 / 1 页')
await page.getByLabel('生成记录每页条数').selectOption('5');await idle()
await page.getByLabel('按学校查找记录').selectOption('分页甲校');await idle()
await expect(page.locator('.history-range')).toContainText('共 7 条')
await page.getByRole('button',{name:'生成记录下一页',exact:true}).click();await idle()
await expect(rows()).toHaveCount(2);await expect(position()).toHaveText('第 2 / 2 页')
await page.getByLabel('搜索生成记录').fill('学生01');await idle()
await expect(rows()).toHaveCount(1);await expect(position()).toHaveText('第 1 / 1 页')
await expect(page.locator('.history-match')).toContainText('学生01')
await page.getByRole('button',{name:'清空筛选',exact:true}).click();await idle()
await expect(rows()).toHaveCount(5);await expect(page.locator('.history-range')).toContainText('共 13 条')
checks.push('Page-size changes and filters reset to page one; matching totals span all pages')
await page.getByLabel('生成记录每页条数').selectOption('10');await idle()
await page.getByRole('button',{name:'收据工作台',exact:true}).click()
await page.getByRole('button',{name:/^生成记录/}).click();await idle()
await expect(rows()).toHaveCount(10);await expect(page.getByLabel('生成记录每页条数')).toHaveValue('10')
await desktop.close();desktop=undefined
page=await launch();await idle()
await expect(rows()).toHaveCount(10);await expect(page.getByLabel('生成记录每页条数')).toHaveValue('10')
expect((await page.evaluate(()=>window.receipts.bootstrap())).settings.historyPageSize).toBe(10)
checks.push('Rows per page remembered across navigation and application restart')
await page.getByLabel('生成记录每页条数').selectOption('5');await idle()
await page.getByRole('button',{name:'生成记录末页',exact:true}).click();await idle()
for(let remaining=12;remaining>=10;remaining--){
await rows().last().getByRole('button',{name:'删除',exact:true}).click()
await page.getByRole('alertdialog').getByRole('button',{name:'删除记录',exact:true}).click();await idle()
await expect(page.locator('.history-range')).toContainText('共 '+remaining+' 条')
}
await expect(position()).toHaveText('第 2 / 2 页');await expect(rows()).toHaveCount(5)
expect((await page.evaluate(()=>window.receipts.bootstrap())).nextNumber).toBe('PAGE00000014')
await page.getByLabel('搜索生成记录').fill('没有对应的学生');await idle()
await expect(rows()).toHaveCount(0);await expect(position()).toHaveText('第 1 / 1 页')
await expect(page.locator('.history-range')).toContainText('共 0 条')
await expect(page.getByRole('button',{name:'生成记录下一页',exact:true})).toBeDisabled()
checks.push('Deleting the last page clamps to a valid page; no-result filters and issued numbers remain correct')
expect(errors).toEqual([])
await fs.writeFile(path.join(qa,'result.json'),JSON.stringify({passed:true,checks,rendererErrors:errors},null,2))
console.log('History pagination verified: '+checks.join('; '))
}finally{
if(desktop){await desktop.evaluate(({app})=>app.exit(0)).catch(()=>{});await desktop.close().catch(()=>{})}
}