@qlover/create-app 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +13 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +48 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +16 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +14 -0
- package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +92 -0
- package/dist/templates/react-app/__tests__/setup/index.ts +51 -0
- package/dist/templates/react-app/__tests__/src/App.test.tsx +139 -0
- package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +288 -0
- package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +102 -0
- package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +228 -0
- package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +207 -0
- package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +181 -0
- package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +61 -0
- package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +199 -0
- package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +192 -0
- package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +235 -0
- package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +224 -0
- package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +257 -0
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +72 -0
- package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +62 -0
- package/dist/templates/react-app/__tests__/src/main.test.tsx +46 -0
- package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +88 -0
- package/dist/templates/react-app/config/app.router.ts +155 -0
- package/dist/templates/react-app/config/common.ts +9 -1
- package/dist/templates/react-app/docs/en/test-guide.md +782 -0
- package/dist/templates/react-app/docs/zh/test-guide.md +782 -0
- package/dist/templates/react-app/package.json +8 -19
- package/dist/templates/react-app/src/base/cases/AppConfig.ts +16 -9
- package/dist/templates/react-app/src/base/cases/PublicAssetsPath.ts +7 -1
- package/dist/templates/react-app/src/base/services/I18nService.ts +15 -4
- package/dist/templates/react-app/src/base/services/RouteService.ts +43 -7
- package/dist/templates/react-app/src/core/bootstraps/BootstrapApp.ts +31 -10
- package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +1 -1
- package/dist/templates/react-app/src/core/globals.ts +1 -3
- package/dist/templates/react-app/src/core/registers/RegisterCommon.ts +5 -3
- package/dist/templates/react-app/src/main.tsx +6 -1
- package/dist/templates/react-app/src/pages/404.tsx +0 -1
- package/dist/templates/react-app/src/pages/500.tsx +1 -1
- package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -1
- package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +9 -2
- package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +5 -3
- package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +4 -6
- package/dist/templates/react-app/tsconfig.json +2 -1
- package/dist/templates/react-app/tsconfig.test.json +13 -0
- package/dist/templates/react-app/vite.config.ts +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @qlover/create-app
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
#### ✨ Features
|
|
8
|
+
|
|
9
|
+
- **router:** implement locale routing and enhance route configuration ([c6ceec2](https://github.com/qlover/fe-base/commit/c6ceec28f83a6c3638872bb4bbb4d6534b02fca8)) ([#476](https://github.com/qlover/fe-base/pull/476))
|
|
10
|
+
- Added support for locale-based routing in the application, allowing routes to include language prefixes.
|
|
11
|
+
- Introduced `baseNoLocaleRoutes` to manage routes without localization, improving flexibility in route handling.
|
|
12
|
+
- Updated `I18nService` to conditionally cache user language based on locale routing settings.
|
|
13
|
+
- Enhanced `RouteService` to handle locale routes and added methods for redirection and language validation.
|
|
14
|
+
- Modified `RegisterCommon` to utilize the appropriate route configuration based on locale settings.
|
|
15
|
+
- Updated various components and hooks to integrate with the new routing logic, ensuring seamless navigation and language handling.
|
|
16
|
+
|
|
17
|
+
These changes aim to improve the user experience by providing localized routes and better language management.
|
|
18
|
+
|
|
19
|
+
Co-authored-by: QRJ <renjie.qin@brain.im>
|
|
20
|
+
|
|
3
21
|
## 0.6.3
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -8,4 +8,4 @@ ${t}`,dn=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),_n=Objec
|
|
|
8
8
|
`))this.#e+=Math.max(1,Math.ceil(Ye(D,{countAnsiEscapeCodes:!0})/t))}get isEnabled(){return this.#a&&!this.#F}set isEnabled(t){if(typeof t!="boolean")throw new TypeError("The `isEnabled` option must be a boolean");this.#a=t}get isSilent(){return this.#F}set isSilent(t){if(typeof t!="boolean")throw new TypeError("The `isSilent` option must be a boolean");this.#F=t}frame(){let t=Date.now();(this.#i===-1||t-this.#c>=this.interval)&&(this.#i=++this.#i%this.#D.frames.length,this.#c=t);let{frames:r}=this.#D,u=r[this.#i];this.color&&(u=d[this.color](u));let i=typeof this.#s=="string"&&this.#s!==""?this.#s+" ":"",D=typeof this.text=="string"?" "+this.text:"",o=typeof this.#o=="string"&&this.#o!==""?" "+this.#o:"";return i+u+D+o}clear(){if(!this.#a||!this.#u.isTTY)return this;this.#u.cursorTo(0);for(let t=0;t<this.#n;t++)t>0&&this.#u.moveCursor(0,-1),this.#u.clearLine(1);return(this.#l||this.lastIndent!==this.#l)&&this.#u.cursorTo(this.#l),this.lastIndent=this.#l,this.#n=0,this}render(){return this.#F?this:(this.clear(),this.#u.write(this.frame()),this.#n=this.#e,this)}start(t){return t&&(this.text=t),this.#F?this:this.#a?this.isSpinning?this:(this.#t.hideCursor&&$e.hide(this.#u),this.#t.discardStdin&&V.default.stdin.isTTY&&(this.#r=!0,Je.start()),this.render(),this.#f=setInterval(this.render.bind(this),this.interval),this):(this.text&&this.#u.write(`- ${this.text}
|
|
9
9
|
`),this)}stop(){return this.#a?(clearInterval(this.#f),this.#f=void 0,this.#i=0,this.clear(),this.#t.hideCursor&&$e.show(this.#u),this.#t.discardStdin&&V.default.stdin.isTTY&&this.#r&&(Je.stop(),this.#r=!1),this):this}succeed(t){return this.stopAndPersist({symbol:H.success,text:t})}fail(t){return this.stopAndPersist({symbol:H.error,text:t})}warn(t){return this.stopAndPersist({symbol:H.warning,text:t})}info(t){return this.stopAndPersist({symbol:H.info,text:t})}stopAndPersist(t={}){if(this.#F)return this;let r=t.prefixText??this.#s,u=this.#E(r," "),i=t.symbol??" ",D=t.text??this.text,s=typeof D=="string"?(i?" ":"")+D:"",a=t.suffixText??this.#o,f=this.#g(a," "),p=u+i+s+f+`
|
|
10
10
|
`;return this.stop(),this.#u.write(p),this}};function tr(e){return new Ze(e)}async function rr(e,t){let r=typeof e=="function",u=typeof e.then=="function";if(!r&&!u)throw new TypeError("Parameter `action` must be a Function or a Promise");let{successText:i,failText:D}=typeof t=="object"?t:{successText:void 0,failText:void 0},o=tr(t).start();try{let a=await(r?e(o):e);return o.succeed(i===void 0?void 0:typeof i=="string"?i:i(a)),a}catch(s){throw o.fail(D===void 0?void 0:typeof D=="string"?D:D(s)),s}}var HD=require("fs");var P=require("path"),me=require("fs"),cr=h(lr(),1),Dt=require("fs");var de=require("fs"),X=class{static ensureDir(t){(0,de.existsSync)(t)||(0,de.mkdirSync)(t,{recursive:!0})}};var{copyFile:rs,stat:us}=Dt.promises,_e=class e{constructor(t,r=e.IGNORE_FILE){this.ignoreTargetPath=t;this.ignoreFile=r}static IGNORE_FILE=".gitignore.template";getIg(t=this.ignoreTargetPath){let r=(0,P.join)(t,this.ignoreFile);if(!(0,me.existsSync)(r))return;let D=(0,me.readFileSync)(r,"utf8").split(`
|
|
11
|
-
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,cr.default)().add(D)}async copyFiles(t,r,u,i){let D=await Dt.promises.readdir(t);await Promise.all(D.map(async o=>{let s=(0,P.join)(t,o),a=(0,P.join)(r,o);if(u&&u.ignores(o))return;if(X.ensureDir((0,P.dirname)(a)),(await us(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await rs(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){X.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var L=require("fs"),kD=h(LD(),1),xe=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return(0,L.readFileSync)(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){(0,L.writeFileSync)(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,kD.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return(0,L.existsSync)(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var $D=["pack-app"],ye=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!(0,HD.existsSync)(r))throw new Error("template path not exit");this.ora=rr,this.context=new UD.FeScriptContext(t),this.subPackages=["node-lib","react-app"],this.copyer=new _e((0,v.join)(this.context.options.configsRootPath,"_common")),this.compose=new xe}get logger(){return this.context.logger}async steps(t){try{return await WD.default.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return $D.includes(t)}async getGeneratorContext(){let t=Ot(this.subPackages,$D),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=wt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=(0,v.join)(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:(0,v.join)(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:(0,v.join)(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=(0,v.join)(D,o),a=(0,v.join)(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var bt={name:"@qlover/create-app",version:"0.
|
|
11
|
+
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,cr.default)().add(D)}async copyFiles(t,r,u,i){let D=await Dt.promises.readdir(t);await Promise.all(D.map(async o=>{let s=(0,P.join)(t,o),a=(0,P.join)(r,o);if(u&&u.ignores(o))return;if(X.ensureDir((0,P.dirname)(a)),(await us(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await rs(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){X.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var L=require("fs"),kD=h(LD(),1),xe=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return(0,L.readFileSync)(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){(0,L.writeFileSync)(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,kD.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return(0,L.existsSync)(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var $D=["pack-app"],ye=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!(0,HD.existsSync)(r))throw new Error("template path not exit");this.ora=rr,this.context=new UD.FeScriptContext(t),this.subPackages=["node-lib","react-app"],this.copyer=new _e((0,v.join)(this.context.options.configsRootPath,"_common")),this.compose=new xe}get logger(){return this.context.logger}async steps(t){try{return await WD.default.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return $D.includes(t)}async getGeneratorContext(){let t=Ot(this.subPackages,$D),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=wt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=(0,v.join)(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:(0,v.join)(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:(0,v.join)(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=(0,v.join)(D,o),a=(0,v.join)(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var bt={name:"@qlover/create-app",version:"0.7.0",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function Ul(){let e=new YD.Command;return e.version(bt.version,"-v, --version","Show version").description(bt.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function Wl(){let{dryRun:e,verbose:t,...r}=Ul(),u=(0,vt.resolve)("./templates"),i=(0,vt.resolve)("./configs");(0,xt.existsSync)(u)||(console.error("Template is empty!"),process.exit(1)),(0,xt.existsSync)(i)||(console.error("Configs is empty!"),process.exit(1)),await new ye({dryRun:e,verbose:t,options:{...r,templateRootPath:u,configsRootPath:i}}).generate()}Wl().catch(e=>{console.error(e),process.exit(1)});
|
package/dist/index.js
CHANGED
|
@@ -8,4 +8,4 @@ ${t}`,cn=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),fn=Objec
|
|
|
8
8
|
`))this.#e+=Math.max(1,Math.ceil(Le(D,{countAnsiEscapeCodes:!0})/t))}get isEnabled(){return this.#a&&!this.#F}set isEnabled(t){if(typeof t!="boolean")throw new TypeError("The `isEnabled` option must be a boolean");this.#a=t}get isSilent(){return this.#F}set isSilent(t){if(typeof t!="boolean")throw new TypeError("The `isSilent` option must be a boolean");this.#F=t}frame(){let t=Date.now();(this.#i===-1||t-this.#c>=this.interval)&&(this.#i=++this.#i%this.#D.frames.length,this.#c=t);let{frames:r}=this.#D,u=r[this.#i];this.color&&(u=E[this.color](u));let i=typeof this.#s=="string"&&this.#s!==""?this.#s+" ":"",D=typeof this.text=="string"?" "+this.text:"",o=typeof this.#o=="string"&&this.#o!==""?" "+this.#o:"";return i+u+D+o}clear(){if(!this.#a||!this.#u.isTTY)return this;this.#u.cursorTo(0);for(let t=0;t<this.#n;t++)t>0&&this.#u.moveCursor(0,-1),this.#u.clearLine(1);return(this.#l||this.lastIndent!==this.#l)&&this.#u.cursorTo(this.#l),this.lastIndent=this.#l,this.#n=0,this}render(){return this.#F?this:(this.clear(),this.#u.write(this.frame()),this.#n=this.#e,this)}start(t){return t&&(this.text=t),this.#F?this:this.#a?this.isSpinning?this:(this.#t.hideCursor&&je.hide(this.#u),this.#t.discardStdin&&ce.stdin.isTTY&&(this.#r=!0,We.start()),this.render(),this.#f=setInterval(this.render.bind(this),this.interval),this):(this.text&&this.#u.write(`- ${this.text}
|
|
9
9
|
`),this)}stop(){return this.#a?(clearInterval(this.#f),this.#f=void 0,this.#i=0,this.clear(),this.#t.hideCursor&&je.show(this.#u),this.#t.discardStdin&&ce.stdin.isTTY&&this.#r&&(We.stop(),this.#r=!1),this):this}succeed(t){return this.stopAndPersist({symbol:k.success,text:t})}fail(t){return this.stopAndPersist({symbol:k.error,text:t})}warn(t){return this.stopAndPersist({symbol:k.warning,text:t})}info(t){return this.stopAndPersist({symbol:k.info,text:t})}stopAndPersist(t={}){if(this.#F)return this;let r=t.prefixText??this.#s,u=this.#E(r," "),i=t.symbol??" ",D=t.text??this.text,s=typeof D=="string"?(i?" ":"")+D:"",a=t.suffixText??this.#o,f=this.#g(a," "),p=u+i+s+f+`
|
|
10
10
|
`;return this.stop(),this.#u.write(p),this}};function zt(e){return new He(e)}async function Kt(e,t){let r=typeof e=="function",u=typeof e.then=="function";if(!r&&!u)throw new TypeError("Parameter `action` must be a Function or a Promise");let{successText:i,failText:D}=typeof t=="object"?t:{successText:void 0,failText:void 0},o=zt(t).start();try{let a=await(r?e(o):e);return o.succeed(i===void 0?void 0:typeof i=="string"?i:i(a)),a}catch(s){throw o.fail(D===void 0?void 0:typeof D=="string"?D:D(s)),s}}import{existsSync as Hl}from"fs";var Dr=ue(ir(),1);import{dirname as Jn,join as Ze}from"path";import{existsSync as Zn,readFileSync as Qn}from"fs";import{promises as nr}from"fs";import{existsSync as Kn,mkdirSync as Xn}from"fs";var H=class{static ensureDir(t){Kn(t)||Xn(t,{recursive:!0})}};var{copyFile:es,stat:ts}=nr,Ce=class e{constructor(t,r=e.IGNORE_FILE){this.ignoreTargetPath=t;this.ignoreFile=r}static IGNORE_FILE=".gitignore.template";getIg(t=this.ignoreTargetPath){let r=Ze(t,this.ignoreFile);if(!Zn(r))return;let D=Qn(r,"utf8").split(`
|
|
11
|
-
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,Dr.default)().add(D)}async copyFiles(t,r,u,i){let D=await nr.readdir(t);await Promise.all(D.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(Jn(a)),(await ts(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await es(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var ID=ue(qD(),1);import{readFileSync as Ll,writeFileSync as kl,existsSync as $l}from"fs";var me=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return Ll(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){kl(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,ID.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return $l(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var jD=["pack-app"],Be=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!Hl(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Ul(t),this.subPackages=["node-lib","react-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new me}get logger(){return this.context.logger}async steps(t){try{return await Wl.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return jD.includes(t)}async getGeneratorContext(){let t=_t(this.subPackages,jD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=mt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=N(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:N(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:N(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=N(D,o),a=N(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var Et={name:"@qlover/create-app",version:"0.
|
|
11
|
+
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,Dr.default)().add(D)}async copyFiles(t,r,u,i){let D=await nr.readdir(t);await Promise.all(D.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(Jn(a)),(await ts(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await es(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var ID=ue(qD(),1);import{readFileSync as Ll,writeFileSync as kl,existsSync as $l}from"fs";var me=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return Ll(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){kl(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,ID.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return $l(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var jD=["pack-app"],Be=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!Hl(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Ul(t),this.subPackages=["node-lib","react-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new me}get logger(){return this.context.logger}async steps(t){try{return await Wl.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return jD.includes(t)}async getGeneratorContext(){let t=_t(this.subPackages,jD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=mt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=N(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:N(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:N(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=N(D,o),a=N(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var Et={name:"@qlover/create-app",version:"0.7.0",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function zl(){let e=new Vl;return e.version(Et.version,"-v, --version","Show version").description(Et.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function Kl(){let{dryRun:e,verbose:t,...r}=zl(),u=ND("./templates"),i=ND("./configs");GD(u)||(console.error("Template is empty!"),process.exit(1)),GD(i)||(console.error("Configs is empty!"),process.exit(1)),await new Be({dryRun:e,verbose:t,options:{...r,templateRootPath:u,configsRootPath:i}}).generate()}Kl().catch(e=>{console.error(e),process.exit(1)});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { I18nService } from '@/base/services/I18nService';
|
|
3
|
+
|
|
4
|
+
export class MockI18nService extends I18nService {
|
|
5
|
+
constructor() {
|
|
6
|
+
super('/');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
t = vi.fn((key: string) => key);
|
|
10
|
+
changeLanguage = vi.fn();
|
|
11
|
+
changeLoading = vi.fn();
|
|
12
|
+
onBefore = vi.fn();
|
|
13
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { EnvConfigInterface } from '@qlover/corekit-bridge';
|
|
2
|
+
import { name, version } from '../../package.json';
|
|
3
|
+
|
|
4
|
+
export class MockAppConfig implements EnvConfigInterface {
|
|
5
|
+
appName = name;
|
|
6
|
+
|
|
7
|
+
appVersion = version;
|
|
8
|
+
|
|
9
|
+
env: string = import.meta.env.MODE;
|
|
10
|
+
|
|
11
|
+
userTokenStorageKey = '__fe_user_token__';
|
|
12
|
+
|
|
13
|
+
userInfoStorageKey = '__fe_user_info__';
|
|
14
|
+
|
|
15
|
+
openAiModels = [
|
|
16
|
+
'gpt-4o-mini',
|
|
17
|
+
'gpt-3.5-turbo',
|
|
18
|
+
'gpt-3.5-turbo-2',
|
|
19
|
+
'gpt-4',
|
|
20
|
+
'gpt-4-32k'
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
openAiBaseUrl = '';
|
|
24
|
+
|
|
25
|
+
openAiToken = '';
|
|
26
|
+
|
|
27
|
+
openAiTokenPrefix = '';
|
|
28
|
+
|
|
29
|
+
openAiRequireToken = true;
|
|
30
|
+
|
|
31
|
+
loginUser = '';
|
|
32
|
+
|
|
33
|
+
loginPassword = '';
|
|
34
|
+
|
|
35
|
+
feApiBaseUrl = '';
|
|
36
|
+
|
|
37
|
+
userApiBaseUrl = '';
|
|
38
|
+
|
|
39
|
+
aiApiBaseUrl = 'https://api.openai.com/v1';
|
|
40
|
+
|
|
41
|
+
aiApiToken = '';
|
|
42
|
+
|
|
43
|
+
aiApiTokenPrefix = 'Bearer';
|
|
44
|
+
|
|
45
|
+
aiApiRequireToken = true;
|
|
46
|
+
|
|
47
|
+
bootHref = '';
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { InteractionHubInterface } from '@/base/port/InteractionHubInterface';
|
|
2
|
+
import { AntdStaticApiInterface } from '@brain-toolkit/antd-theme-override/react';
|
|
3
|
+
import { vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
export class MockDialogHandler
|
|
6
|
+
implements InteractionHubInterface, AntdStaticApiInterface
|
|
7
|
+
{
|
|
8
|
+
setMessage = vi.fn();
|
|
9
|
+
setModal = vi.fn();
|
|
10
|
+
setNotification = vi.fn();
|
|
11
|
+
success = vi.fn();
|
|
12
|
+
error = vi.fn();
|
|
13
|
+
info = vi.fn();
|
|
14
|
+
warn = vi.fn();
|
|
15
|
+
confirm = vi.fn();
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { LoggerInterface } from '@qlover/logger';
|
|
2
|
+
import { vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
export class MockLogger implements LoggerInterface {
|
|
5
|
+
info = vi.fn();
|
|
6
|
+
error = vi.fn();
|
|
7
|
+
debug = vi.fn();
|
|
8
|
+
warn = vi.fn();
|
|
9
|
+
trace = vi.fn();
|
|
10
|
+
log = vi.fn();
|
|
11
|
+
fatal = vi.fn();
|
|
12
|
+
addAppender = vi.fn();
|
|
13
|
+
context = vi.fn();
|
|
14
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { MockLogger } from './MockLogger';
|
|
3
|
+
import { MockAppConfig } from './MockAppConfit';
|
|
4
|
+
import { MockDialogHandler } from './MockDialogHandler';
|
|
5
|
+
|
|
6
|
+
export function createMockGlobals() {
|
|
7
|
+
const mockLogger = new MockLogger();
|
|
8
|
+
const mockAppConfig = new MockAppConfig();
|
|
9
|
+
const mockDialogHandler = new MockDialogHandler();
|
|
10
|
+
|
|
11
|
+
// Mock JSON serializer
|
|
12
|
+
const mockJSON = {
|
|
13
|
+
serialize: vi.fn((data) => JSON.stringify(data)),
|
|
14
|
+
deserialize: vi.fn((data) => JSON.parse(data)),
|
|
15
|
+
stringify: vi.fn((data) => JSON.stringify(data)),
|
|
16
|
+
parse: vi.fn((data) => JSON.parse(data))
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
const storageOptions: Record<string, any> = {
|
|
21
|
+
localStorage: {},
|
|
22
|
+
cookieStorage: {},
|
|
23
|
+
localStorageEncrypt: {}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Mock localStorage with actual storage simulation
|
|
27
|
+
const mockLocalStorageData = storageOptions.localStorage ?? {};
|
|
28
|
+
const mockLocalStorage = {
|
|
29
|
+
get: vi.fn((key: string) => mockLocalStorageData[key]),
|
|
30
|
+
set: vi.fn((key: string, value: unknown) => {
|
|
31
|
+
mockLocalStorageData[key] = value;
|
|
32
|
+
}),
|
|
33
|
+
getItem: vi.fn((key: string) => mockLocalStorageData[key]),
|
|
34
|
+
setItem: vi.fn((key: string, value: unknown) => {
|
|
35
|
+
mockLocalStorageData[key] = value;
|
|
36
|
+
}),
|
|
37
|
+
remove: vi.fn((key: string) => {
|
|
38
|
+
delete mockLocalStorageData[key];
|
|
39
|
+
}),
|
|
40
|
+
removeItem: vi.fn((key: string) => {
|
|
41
|
+
delete mockLocalStorageData[key];
|
|
42
|
+
}),
|
|
43
|
+
clear: vi.fn(() => {
|
|
44
|
+
Object.keys(mockLocalStorageData).forEach((key) => {
|
|
45
|
+
delete mockLocalStorageData[key];
|
|
46
|
+
});
|
|
47
|
+
}),
|
|
48
|
+
has: vi.fn((key: string) => key in mockLocalStorageData),
|
|
49
|
+
keys: vi.fn(() => Object.keys(mockLocalStorageData)),
|
|
50
|
+
size: vi.fn(() => Object.keys(mockLocalStorageData).length)
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Mock localStorageEncrypt (same as localStorage for now)
|
|
54
|
+
const mockLocalStorageEncrypt = { ...mockLocalStorage };
|
|
55
|
+
|
|
56
|
+
// Mock cookieStorage
|
|
57
|
+
const mockCookieStorageData = storageOptions.cookieStorage ?? {};
|
|
58
|
+
const mockCookieStorage = {
|
|
59
|
+
get: vi.fn((key: string) => mockCookieStorageData[key]),
|
|
60
|
+
set: vi.fn((key: string, value: unknown, _options?: unknown) => {
|
|
61
|
+
mockCookieStorageData[key] = value;
|
|
62
|
+
}),
|
|
63
|
+
getItem: vi.fn((key: string) => mockCookieStorageData[key]),
|
|
64
|
+
setItem: vi.fn((key: string, value: unknown, _options?: unknown) => {
|
|
65
|
+
mockCookieStorageData[key] = value;
|
|
66
|
+
}),
|
|
67
|
+
remove: vi.fn((key: string) => {
|
|
68
|
+
delete mockCookieStorageData[key];
|
|
69
|
+
}),
|
|
70
|
+
removeItem: vi.fn((key: string) => {
|
|
71
|
+
delete mockCookieStorageData[key];
|
|
72
|
+
}),
|
|
73
|
+
clear: vi.fn(() => {
|
|
74
|
+
Object.keys(mockCookieStorageData).forEach((key) => {
|
|
75
|
+
delete mockCookieStorageData[key];
|
|
76
|
+
});
|
|
77
|
+
}),
|
|
78
|
+
has: vi.fn((key: string) => key in mockCookieStorageData),
|
|
79
|
+
keys: vi.fn(() => Object.keys(mockCookieStorageData)),
|
|
80
|
+
size: vi.fn(() => Object.keys(mockCookieStorageData).length)
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
logger: mockLogger,
|
|
85
|
+
appConfig: mockAppConfig,
|
|
86
|
+
dialogHandler: mockDialogHandler,
|
|
87
|
+
JSON: mockJSON,
|
|
88
|
+
localStorage: mockLocalStorage,
|
|
89
|
+
localStorageEncrypt: mockLocalStorageEncrypt,
|
|
90
|
+
cookieStorage: mockCookieStorage
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createMockGlobals } from '../__mocks__/createMockGlobals';
|
|
2
|
+
|
|
3
|
+
// 设置测试环境
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
// 清理DOM
|
|
6
|
+
document.body.innerHTML = '';
|
|
7
|
+
|
|
8
|
+
// 创建root元素(如果不存在)
|
|
9
|
+
if (!document.getElementById('root')) {
|
|
10
|
+
const rootElement = document.createElement('div');
|
|
11
|
+
rootElement.id = 'root';
|
|
12
|
+
document.body.appendChild(rootElement);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
// 清理所有mock
|
|
18
|
+
vi.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// 全局测试配置
|
|
22
|
+
global.ResizeObserver = vi.fn().mockImplementation(() => ({
|
|
23
|
+
observe: vi.fn(),
|
|
24
|
+
unobserve: vi.fn(),
|
|
25
|
+
disconnect: vi.fn()
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// Mock window.matchMedia
|
|
29
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
30
|
+
writable: true,
|
|
31
|
+
value: vi.fn().mockImplementation((query) => ({
|
|
32
|
+
matches: false,
|
|
33
|
+
media: query,
|
|
34
|
+
onchange: null,
|
|
35
|
+
addListener: vi.fn(), // deprecated
|
|
36
|
+
removeListener: vi.fn(), // deprecated
|
|
37
|
+
addEventListener: vi.fn(),
|
|
38
|
+
removeEventListener: vi.fn(),
|
|
39
|
+
dispatchEvent: vi.fn()
|
|
40
|
+
}))
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Mock IntersectionObserver
|
|
44
|
+
global.IntersectionObserver = vi.fn().mockImplementation(() => ({
|
|
45
|
+
observe: vi.fn(),
|
|
46
|
+
unobserve: vi.fn(),
|
|
47
|
+
disconnect: vi.fn()
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// Mock globals
|
|
51
|
+
vi.mock('@/core/globals', () => createMockGlobals());
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import App from '@/App';
|
|
3
|
+
import { routerPrefix } from '@config/common';
|
|
4
|
+
|
|
5
|
+
// Mock the dependencies
|
|
6
|
+
vi.mock('react-router-dom', async () => {
|
|
7
|
+
const actual = await vi.importActual('react-router-dom');
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
createBrowserRouter: vi.fn(() => ({
|
|
11
|
+
routes: [],
|
|
12
|
+
basename: routerPrefix
|
|
13
|
+
})),
|
|
14
|
+
RouterProvider: vi.fn(({ router }) => (
|
|
15
|
+
<div data-testid="router-provider">
|
|
16
|
+
<div data-testid="router-basename">{router.basename}</div>
|
|
17
|
+
<div data-testid="router-routes-count">
|
|
18
|
+
{router.routes?.length || 0}
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
))
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
vi.mock('@brain-toolkit/antd-theme-override/react', () => ({
|
|
26
|
+
AntdThemeProvider: vi.fn(({ children, theme }) => (
|
|
27
|
+
<div data-testid="antd-theme-provider">
|
|
28
|
+
<div data-testid="theme-key">{theme?.cssVar?.key}</div>
|
|
29
|
+
<div data-testid="theme-prefix">{theme?.cssVar?.prefix}</div>
|
|
30
|
+
{children}
|
|
31
|
+
</div>
|
|
32
|
+
))
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
vi.mock('@/core/IOC', () => ({
|
|
36
|
+
IOC: vi.fn((service) => {
|
|
37
|
+
if (service === 'RouteService') {
|
|
38
|
+
return {
|
|
39
|
+
routes: [
|
|
40
|
+
{ path: '/', component: 'HomePage' },
|
|
41
|
+
{ path: '/about', component: 'AboutPage' }
|
|
42
|
+
]
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (service === 'DialogHandler') {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
return {};
|
|
49
|
+
})
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
vi.mock('@/uikit/hooks/useStore', () => ({
|
|
53
|
+
useStore: vi.fn((service, selector) => {
|
|
54
|
+
if (service && selector) {
|
|
55
|
+
return [
|
|
56
|
+
{ path: '/', component: 'HomePage' },
|
|
57
|
+
{ path: '/about', component: 'AboutPage' }
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
return [];
|
|
61
|
+
})
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
vi.mock('@/base/cases/RouterLoader', () => ({
|
|
65
|
+
RouterLoader: vi.fn().mockImplementation(() => ({
|
|
66
|
+
toRoute: vi.fn((route) => ({
|
|
67
|
+
path: route.path,
|
|
68
|
+
element: <div data-testid={`route-${route.path}`}>{route.component}</div>
|
|
69
|
+
}))
|
|
70
|
+
}))
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
// Mock the CSS import
|
|
74
|
+
vi.mock('@/styles/css/index.css', () => ({}));
|
|
75
|
+
|
|
76
|
+
describe('App Component', () => {
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
vi.clearAllMocks();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should render without crashing', () => {
|
|
82
|
+
render(<App />);
|
|
83
|
+
|
|
84
|
+
// Check if the main app structure is rendered
|
|
85
|
+
expect(screen.getByTestId('antd-theme-provider')).toBeDefined();
|
|
86
|
+
expect(screen.getByTestId('router-provider')).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should configure theme provider correctly', () => {
|
|
90
|
+
render(<App />);
|
|
91
|
+
|
|
92
|
+
// Check theme configuration
|
|
93
|
+
expect(screen.getByTestId('theme-key').textContent).toBe('fe-theme');
|
|
94
|
+
expect(screen.getByTestId('theme-prefix').textContent).toBe('fe');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should configure router with correct basename', () => {
|
|
98
|
+
render(<App />);
|
|
99
|
+
|
|
100
|
+
// Check router configuration
|
|
101
|
+
expect(screen.getByTestId('router-basename')).toBeDefined();
|
|
102
|
+
expect(screen.getByTestId('router-basename').textContent).toBe(
|
|
103
|
+
routerPrefix
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should render router provider with routes', () => {
|
|
108
|
+
render(<App />);
|
|
109
|
+
|
|
110
|
+
// Check that router provider is rendered
|
|
111
|
+
expect(screen.getByTestId('router-provider')).toBeDefined();
|
|
112
|
+
expect(screen.getByTestId('router-routes-count')).toBeDefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should have proper component structure', () => {
|
|
116
|
+
const { container } = render(<App />);
|
|
117
|
+
|
|
118
|
+
// Check the overall structure
|
|
119
|
+
expect(container.firstChild).toBeDefined();
|
|
120
|
+
|
|
121
|
+
// Check that theme provider wraps router provider
|
|
122
|
+
const themeProvider = screen.getByTestId('antd-theme-provider');
|
|
123
|
+
const routerProvider = screen.getByTestId('router-provider');
|
|
124
|
+
|
|
125
|
+
expect(themeProvider.contains(routerProvider)).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should handle empty routes gracefully', async () => {
|
|
129
|
+
// Mock useStore to return empty routes
|
|
130
|
+
const { useStore } = await import('@/uikit/hooks/useStore');
|
|
131
|
+
vi.mocked(useStore).mockReturnValueOnce([]);
|
|
132
|
+
|
|
133
|
+
render(<App />);
|
|
134
|
+
|
|
135
|
+
// Should still render without crashing
|
|
136
|
+
expect(screen.getByTestId('antd-theme-provider')).toBeDefined();
|
|
137
|
+
expect(screen.getByTestId('router-provider')).toBeDefined();
|
|
138
|
+
});
|
|
139
|
+
});
|