@qlover/create-app 0.8.0 → 0.9.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 +17 -0
- package/dist/configs/_common/.github/workflows/general-check.yml +1 -1
- package/dist/configs/_common/.github/workflows/release.yml +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/react-app/__tests__/src/uikit/components/chatMessage/ChatRoot.test.tsx +274 -0
- package/dist/templates/react-app/config/IOCIdentifier.ts +3 -0
- package/dist/templates/react-app/config/Identifier/components/component.chatMessage.ts +56 -0
- package/dist/templates/react-app/config/Identifier/components/component.messageBaseList.ts +103 -0
- package/dist/templates/react-app/config/Identifier/pages/index.ts +1 -0
- package/dist/templates/react-app/config/Identifier/pages/page.message.ts +20 -0
- package/dist/templates/react-app/config/app.router.ts +23 -0
- package/dist/templates/react-app/config/i18n/chatMessageI18n.ts +17 -0
- package/dist/templates/react-app/config/i18n/messageBaseListI18n.ts +22 -0
- package/dist/templates/react-app/config/i18n/messageI18n.ts +14 -0
- package/dist/templates/react-app/docs/en/components/chat-message-component.md +314 -0
- package/dist/templates/react-app/docs/en/components/chat-message-refactor.md +270 -0
- package/dist/templates/react-app/docs/en/components/message-base-list-component.md +172 -0
- package/dist/templates/react-app/docs/zh/components/chat-message-component.md +314 -0
- package/dist/templates/react-app/docs/zh/components/chat-message-refactor.md +270 -0
- package/dist/templates/react-app/docs/zh/components/message-base-list-component.md +172 -0
- package/dist/templates/react-app/playwright.config.ts +6 -6
- package/dist/templates/react-app/public/locales/en/common.json +44 -1
- package/dist/templates/react-app/public/locales/zh/common.json +44 -1
- package/dist/templates/react-app/src/pages/base/MessagePage.tsx +40 -0
- package/dist/templates/react-app/src/uikit/components/MessageBaseList.tsx +240 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/ChatMessageBridge.ts +176 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/ChatRoot.tsx +21 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/FocusBar.tsx +106 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessageApi.ts +271 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessageItem.tsx +102 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessagesList.tsx +86 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @qlover/create-app
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
#### ✨ Features
|
|
8
|
+
|
|
9
|
+
- **react-app:** integrate chat message components and enhance messaging functionality ([84af0f3](https://github.com/qlover/fe-base/commit/84af0f3913f191e79d747bffcc7d26283f6b861f)) ([#537](https://github.com/qlover/fe-base/pull/537))
|
|
10
|
+
- Introduced ChatMessage component system, including ChatRoot, MessagesList, and MessageItem for improved chat interactions.
|
|
11
|
+
- Added MessageBaseList component for testing message gateway functionality.
|
|
12
|
+
- Implemented internationalization support for chat and message components, enhancing user experience.
|
|
13
|
+
- Updated routing configuration to include a new MessagePage for managing chat messages.
|
|
14
|
+
- Enhanced Playwright testing setup for chat components, ensuring robust testing coverage.
|
|
15
|
+
|
|
16
|
+
These changes aim to provide a comprehensive chat experience, streamline message handling, and improve overall application functionality.
|
|
17
|
+
|
|
18
|
+
Co-authored-by: QRJ <github-actions[bot]@users.noreply.github.com>
|
|
19
|
+
|
|
3
20
|
## 0.8.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
|
@@ -31,7 +31,7 @@ jobs:
|
|
|
31
31
|
- name: Install Node.js
|
|
32
32
|
uses: actions/setup-node@v2
|
|
33
33
|
with:
|
|
34
|
-
node-version: '
|
|
34
|
+
node-version: '20.16.0'
|
|
35
35
|
|
|
36
36
|
- run: npm install -g pnpm
|
|
37
37
|
- run: pnpm install --frozen-lockfile
|
|
@@ -66,7 +66,7 @@ jobs:
|
|
|
66
66
|
- name: Install Node.js
|
|
67
67
|
uses: actions/setup-node@v2
|
|
68
68
|
with:
|
|
69
|
-
node-version: '
|
|
69
|
+
node-version: '20.16.0'
|
|
70
70
|
|
|
71
71
|
- run: npm install -g pnpm
|
|
72
72
|
- run: pnpm install --frozen-lockfile
|
package/dist/index.cjs
CHANGED
|
@@ -8,4 +8,4 @@ ${t}`,_n=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),mn=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,l=this.#g(a," "),p=u+i+s+l+`
|
|
10
10
|
`;return this.stop(),this.#u.write(p),this}};function rr(e){return new Ze(e)}async function ur(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=rr(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 bt=require("fs");var P=require("path"),me=require("fs"),fr=h(cr(),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:us,stat:is}=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,fr.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 is(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await us(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"),$D=h(kD(),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,$D.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 UD=["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,bt.existsSync)(r))throw new Error("template path not exit");this.ora=ur,this.context=new WD.ScriptContext("create-app",t),this.subPackages=["node-lib","react-app","next-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 HD.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 UD.includes(t)}async getGeneratorContext(){let t=wt(this.subPackages,UD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=Pt(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=(a,l)=>(this.logger.debug("copyCallback",a,l),this.compose.composeConfigFile(t,a,l)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}let s=(0,v.join)(D,u);if(!(0,bt.existsSync)(s)){this.logger.debug(`Config path not found: ${s}`);return}await this.copyer.copyPaths({sourcePath:s,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 vt={name:"@qlover/create-app",version:"0.
|
|
11
|
+
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,fr.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 is(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await us(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"),$D=h(kD(),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,$D.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 UD=["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,bt.existsSync)(r))throw new Error("template path not exit");this.ora=ur,this.context=new WD.ScriptContext("create-app",t),this.subPackages=["node-lib","react-app","next-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 HD.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 UD.includes(t)}async getGeneratorContext(){let t=wt(this.subPackages,UD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=Pt(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=(a,l)=>(this.logger.debug("copyCallback",a,l),this.compose.composeConfigFile(t,a,l)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}let s=(0,v.join)(D,u);if(!(0,bt.existsSync)(s)){this.logger.debug(`Config path not found: ${s}`);return}await this.copyer.copyPaths({sourcePath:s,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 vt={name:"@qlover/create-app",version:"0.9.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:{lint:"eslint src --fix",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 Wl(){let e=new YD.Command;return e.version(vt.version,"-v, --version","Show version").description(vt.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 VD(e=process.cwd()){let{dryRun:t,verbose:r,...u}=Wl(),i=(0,xt.resolve)(e,"./templates"),D=(0,xt.resolve)(e,"./configs");(0,yt.existsSync)(i)||(console.error("Template is empty!"),process.exit(1)),(0,yt.existsSync)(D)||(console.error("Configs is empty!"),process.exit(1)),await new ye({dryRun:t,verbose:r,options:{...u,templateRootPath:i,configsRootPath:D}}).generate()}VD(__dirname).catch(e=>{console.error(e),process.exit(1)});
|
package/dist/index.js
CHANGED
|
@@ -8,4 +8,4 @@ ${t}`,pD=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),hD=Objec
|
|
|
8
8
|
`))this.#e+=Math.max(1,Math.ceil(Le(n,{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.#n.frames.length,this.#c=t);let{frames:r}=this.#n,u=r[this.#i];this.color&&(u=E[this.color](u));let i=typeof this.#s=="string"&&this.#s!==""?this.#s+" ":"",n=typeof this.text=="string"?" "+this.text:"",o=typeof this.#o=="string"&&this.#o!==""?" "+this.#o:"";return i+u+n+o}clear(){if(!this.#a||!this.#u.isTTY)return this;this.#u.cursorTo(0);for(let t=0;t<this.#D;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.#D=0,this}render(){return this.#F?this:(this.clear(),this.#u.write(this.frame()),this.#D=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??" ",n=t.text??this.text,s=typeof n=="string"?(i?" ":"")+n:"",a=t.suffixText??this.#o,l=this.#g(a," "),p=u+i+s+l+`
|
|
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:n}=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(n===void 0?void 0:typeof n=="string"?n:n(s)),s}}import{existsSync as Nn}from"fs";var nr=ue(ir(),1);import{dirname as QD,join as Ze}from"path";import{existsSync as es,readFileSync as ts}from"fs";import{promises as Dr}from"fs";import{existsSync as JD,mkdirSync as ZD}from"fs";var H=class{static ensureDir(t){JD(t)||ZD(t,{recursive:!0})}};var{copyFile:rs,stat:us}=Dr,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(!es(r))return;let n=ts(r,"utf8").split(`
|
|
11
|
-
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,nr.default)().add(n)}async copyFiles(t,r,u,i){let n=await Dr.readdir(t);await Promise.all(n.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(QD(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}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var jn=ue(In(),1);import{readFileSync as $l,writeFileSync as Ul,existsSync as Wl}from"fs";var _e=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 $l(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){Ul(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let n=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof n=="string"?n:JSON.stringify(n))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,jn.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 n=this.getRealTemplateFilePath(u);return Wl(n)?(this.mergeJSONFile(n,JSON.parse(i)),!0):(this.writeFile(n,i),!0)}return this.writeFile(u,i),!0}return!1}};var Gn=["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(!Nn(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Hl("create-app",t),this.subPackages=["node-lib","react-app","next-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new _e}get logger(){return this.context.logger}async steps(t){try{return await Yl.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 Gn.includes(t)}async getGeneratorContext(){let t=mt(this.subPackages,Gn),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=_t(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=(a,l)=>(this.logger.debug("copyCallback",a,l),this.compose.composeConfigFile(t,a,l)),{configsRootPath:n,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}let s=N(n,u);if(!Nn(s)){this.logger.debug(`Config path not found: ${s}`);return}await this.copyer.copyPaths({sourcePath:s,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:n}=this.context.options;for(let o of u){let s=N(n,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,nr.default)().add(n)}async copyFiles(t,r,u,i){let n=await Dr.readdir(t);await Promise.all(n.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(QD(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}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var jn=ue(In(),1);import{readFileSync as $l,writeFileSync as Ul,existsSync as Wl}from"fs";var _e=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 $l(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){Ul(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let n=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof n=="string"?n:JSON.stringify(n))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,jn.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 n=this.getRealTemplateFilePath(u);return Wl(n)?(this.mergeJSONFile(n,JSON.parse(i)),!0):(this.writeFile(n,i),!0)}return this.writeFile(u,i),!0}return!1}};var Gn=["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(!Nn(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Hl("create-app",t),this.subPackages=["node-lib","react-app","next-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new _e}get logger(){return this.context.logger}async steps(t){try{return await Yl.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 Gn.includes(t)}async getGeneratorContext(){let t=mt(this.subPackages,Gn),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=_t(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=(a,l)=>(this.logger.debug("copyCallback",a,l),this.compose.composeConfigFile(t,a,l)),{configsRootPath:n,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}let s=N(n,u);if(!Nn(s)){this.logger.debug(`Config path not found: ${s}`);return}await this.copyer.copyPaths({sourcePath:s,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:n}=this.context.options;for(let o of u){let s=N(n,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.9.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:{lint:"eslint src --fix",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 Kl(){let e=new zl;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 kn(e=process.cwd()){let{dryRun:t,verbose:r,...u}=Kl(),i=Mn(e,"./templates"),n=Mn(e,"./configs");Ln(i)||(console.error("Template is empty!"),process.exit(1)),Ln(n)||(console.error("Configs is empty!"),process.exit(1)),await new Be({dryRun:t,verbose:r,options:{...u,templateRootPath:i,configsRootPath:n}}).generate()}import{fileURLToPath as Xl}from"url";import{dirname as Jl}from"path";var Zl=Xl(import.meta.url),Ql=Jl(Zl);kn(Ql).catch(e=>{console.error(e),process.exit(1)});
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { BootstrapTest } from '@__mocks__/BootstrapTest';
|
|
2
|
+
import { TestApp } from '@__mocks__/components';
|
|
3
|
+
import * as chatI18nKeys from '@config/Identifier/components/component.chatMessage';
|
|
4
|
+
import { ChatMessageRole, ChatMessageStore } from '@qlover/corekit-bridge';
|
|
5
|
+
import { render, screen } from '@testing-library/react';
|
|
6
|
+
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import { logger } from '@/core/globals';
|
|
8
|
+
import { ChatMessageBridge } from '@/uikit/components/chatMessage/ChatMessageBridge';
|
|
9
|
+
import { ChatRoot } from '@/uikit/components/chatMessage/ChatRoot';
|
|
10
|
+
import type { ChatMessageI18nInterface } from '@config/i18n/chatMessageI18n';
|
|
11
|
+
import type { MessageGetwayInterface } from '@qlover/corekit-bridge';
|
|
12
|
+
|
|
13
|
+
// Mock i18n values matching the actual i18n keys
|
|
14
|
+
const mockI18n: ChatMessageI18nInterface = {
|
|
15
|
+
send: chatI18nKeys.COMPONENT_CHAT_SEND,
|
|
16
|
+
stop: chatI18nKeys.COMPONENT_CHAT_STOP,
|
|
17
|
+
loading: chatI18nKeys.COMPONENT_CHAT_LOADING,
|
|
18
|
+
inputPlaceholder: chatI18nKeys.COMPONENT_CHAT_INPUT_PLACEHOLDER,
|
|
19
|
+
empty: chatI18nKeys.COMPONENT_CHAT_EMPTY,
|
|
20
|
+
start: chatI18nKeys.COMPONENT_CHAT_START,
|
|
21
|
+
retry: chatI18nKeys.COMPONENT_CHAT_RETRY,
|
|
22
|
+
duration: chatI18nKeys.COMPONENT_CHAT_DURATION
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Simple mock gateway for testing
|
|
26
|
+
class MockMessageGateway implements MessageGetwayInterface {
|
|
27
|
+
sendMessage = vi.fn();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('ChatRoot Component', () => {
|
|
31
|
+
let messagesStore: ChatMessageStore<string>;
|
|
32
|
+
let mockGateway: MockMessageGateway;
|
|
33
|
+
let bridge: ChatMessageBridge<string>;
|
|
34
|
+
|
|
35
|
+
beforeAll(async () => {
|
|
36
|
+
await BootstrapTest.main({
|
|
37
|
+
root: globalThis,
|
|
38
|
+
bootHref: 'http://localhost:3000/en/test'
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
// Create fresh instances for each test
|
|
44
|
+
messagesStore = new ChatMessageStore<string>();
|
|
45
|
+
mockGateway = new MockMessageGateway();
|
|
46
|
+
bridge = new ChatMessageBridge<string>(messagesStore, {
|
|
47
|
+
gateway: mockGateway,
|
|
48
|
+
logger: logger,
|
|
49
|
+
senderName: 'TestSender',
|
|
50
|
+
gatewayOptions: {}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Reset mocks
|
|
54
|
+
vi.clearAllMocks();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('Component Rendering', () => {
|
|
58
|
+
it('should render ChatRoot with all required components', () => {
|
|
59
|
+
render(
|
|
60
|
+
<TestApp>
|
|
61
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
62
|
+
</TestApp>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Check main container
|
|
66
|
+
const chatRoot = screen.getByTestId('ChatRoot');
|
|
67
|
+
expect(chatRoot).toBeDefined();
|
|
68
|
+
|
|
69
|
+
// Check MessagesList component
|
|
70
|
+
const messagesList = screen.getByTestId('MessagesList');
|
|
71
|
+
expect(messagesList).toBeDefined();
|
|
72
|
+
|
|
73
|
+
// Check FocusBar component
|
|
74
|
+
const focusBar = screen.getByTestId('FocusBar');
|
|
75
|
+
expect(focusBar).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should display empty state initially', () => {
|
|
79
|
+
render(
|
|
80
|
+
<TestApp>
|
|
81
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
82
|
+
</TestApp>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Empty state text should be visible
|
|
86
|
+
// Note: The actual text will be the i18n key, not the translated text in tests
|
|
87
|
+
const emptyText = screen.getByText(/component_chat:empty/i);
|
|
88
|
+
expect(emptyText).toBeDefined();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should render textarea for input', () => {
|
|
92
|
+
render(
|
|
93
|
+
<TestApp>
|
|
94
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
95
|
+
</TestApp>
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const textarea = screen.getByRole('textbox');
|
|
99
|
+
expect(textarea).toBeDefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should render send button', () => {
|
|
103
|
+
render(
|
|
104
|
+
<TestApp>
|
|
105
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
106
|
+
</TestApp>
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const sendButton = screen.getByTestId('FocusBar-Button-Send');
|
|
110
|
+
expect(sendButton).toBeDefined();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('Props Integration', () => {
|
|
115
|
+
it('should accept and use bridge prop', () => {
|
|
116
|
+
render(
|
|
117
|
+
<TestApp>
|
|
118
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
119
|
+
</TestApp>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Verify bridge is connected by checking store state
|
|
123
|
+
const state = messagesStore.state;
|
|
124
|
+
expect(state).toBeDefined();
|
|
125
|
+
expect(state.messages).toEqual([]);
|
|
126
|
+
expect(state.draftMessages).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should accept different bridge instances', () => {
|
|
130
|
+
const newStore = new ChatMessageStore<string>();
|
|
131
|
+
const newBridge = new ChatMessageBridge<string>(newStore, {
|
|
132
|
+
gateway: mockGateway,
|
|
133
|
+
logger: logger,
|
|
134
|
+
senderName: 'NewTestSender',
|
|
135
|
+
gatewayOptions: {}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
render(
|
|
139
|
+
<TestApp>
|
|
140
|
+
<ChatRoot bridge={newBridge} tt={mockI18n} />
|
|
141
|
+
</TestApp>
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const chatRoot = screen.getByTestId('ChatRoot');
|
|
145
|
+
expect(chatRoot).toBeDefined();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('Bridge Integration', () => {
|
|
150
|
+
it('should connect to message store through bridge', () => {
|
|
151
|
+
render(
|
|
152
|
+
<TestApp>
|
|
153
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
154
|
+
</TestApp>
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Bridge should have access to store
|
|
158
|
+
const messageStore = bridge.getMessageStore();
|
|
159
|
+
expect(messageStore).toBe(messagesStore);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should update UI when content is changed via bridge', () => {
|
|
163
|
+
render(
|
|
164
|
+
<TestApp>
|
|
165
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
166
|
+
</TestApp>
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Initially, send button should be disabled (no content)
|
|
170
|
+
const sendButton = screen.getByTestId('FocusBar-Button-Send');
|
|
171
|
+
expect(sendButton).toHaveProperty('disabled', true);
|
|
172
|
+
|
|
173
|
+
// Change content via bridge
|
|
174
|
+
bridge.onChangeContent('Test message');
|
|
175
|
+
|
|
176
|
+
// Now send button should be enabled (has content)
|
|
177
|
+
// Note: This requires the component to re-render based on store updates
|
|
178
|
+
const state = messagesStore.state;
|
|
179
|
+
expect(state.draftMessages.length).toBeGreaterThan(0);
|
|
180
|
+
expect(state.draftMessages[0]?.content).toBe('Test message');
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('Store State Management', () => {
|
|
185
|
+
it('should reflect message store state', () => {
|
|
186
|
+
render(
|
|
187
|
+
<TestApp>
|
|
188
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
189
|
+
</TestApp>
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Initial state should be empty
|
|
193
|
+
const initialState = messagesStore.state;
|
|
194
|
+
expect(initialState.messages).toEqual([]);
|
|
195
|
+
expect(initialState.draftMessages).toEqual([]);
|
|
196
|
+
// These properties might be undefined initially, which is fine
|
|
197
|
+
expect(initialState.disabledSend).toBeFalsy();
|
|
198
|
+
expect(initialState.streaming).toBeFalsy();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should update when messages are added to store', () => {
|
|
202
|
+
render(
|
|
203
|
+
<TestApp>
|
|
204
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
205
|
+
</TestApp>
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Add a message to the store
|
|
209
|
+
messagesStore.addMessage({
|
|
210
|
+
id: 'test-1',
|
|
211
|
+
role: ChatMessageRole.USER,
|
|
212
|
+
content: 'Test message',
|
|
213
|
+
loading: false,
|
|
214
|
+
startTime: Date.now()
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Verify message is in store
|
|
218
|
+
const state = messagesStore.state;
|
|
219
|
+
expect(state.messages.length).toBe(1);
|
|
220
|
+
expect(state.messages[0]?.content).toBe('Test message');
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('Button States', () => {
|
|
225
|
+
it('should have disabled send button when no content', () => {
|
|
226
|
+
render(
|
|
227
|
+
<TestApp>
|
|
228
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
229
|
+
</TestApp>
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const sendButton = screen.getByTestId('FocusBar-Button-Send');
|
|
233
|
+
expect(sendButton).toHaveProperty('disabled', true);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe('Component Structure', () => {
|
|
238
|
+
it('should have correct CSS classes for layout', () => {
|
|
239
|
+
render(
|
|
240
|
+
<TestApp>
|
|
241
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
242
|
+
</TestApp>
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const chatRoot = screen.getByTestId('ChatRoot');
|
|
246
|
+
expect(chatRoot.className).toContain('flex');
|
|
247
|
+
expect(chatRoot.className).toContain('flex-col');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should render MessagesList with correct data-testid', () => {
|
|
251
|
+
render(
|
|
252
|
+
<TestApp>
|
|
253
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
254
|
+
</TestApp>
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const messagesList = screen.getByTestId('MessagesList');
|
|
258
|
+
expect(messagesList).toBeDefined();
|
|
259
|
+
expect(messagesList.className).toContain('overflow-y-auto');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should render FocusBar with correct data-testid', () => {
|
|
263
|
+
render(
|
|
264
|
+
<TestApp>
|
|
265
|
+
<ChatRoot bridge={bridge} tt={mockI18n} />
|
|
266
|
+
</TestApp>
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const focusBar = screen.getByTestId('FocusBar');
|
|
270
|
+
expect(focusBar).toBeDefined();
|
|
271
|
+
expect(focusBar.className).toContain('border-t');
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|
|
@@ -6,6 +6,7 @@ import type { ExecutorPageBridgeInterface } from '@/base/port/ExecutorPageBridge
|
|
|
6
6
|
import type { JSONStoragePageBridgeInterface } from '@/base/port/JSONStoragePageBridgeInterface';
|
|
7
7
|
import type { RequestPageBridgeInterface } from '@/base/port/RequestPageBridgeInterface';
|
|
8
8
|
import type { I18nService } from '@/base/services/I18nService';
|
|
9
|
+
import type { MessageService } from '@/base/services/MessageService';
|
|
9
10
|
import type { ProcesserExecutor } from '@/base/services/ProcesserExecutor';
|
|
10
11
|
import type { RouteService } from '@/base/services/RouteService';
|
|
11
12
|
import type { UserService } from '@/base/services/UserService';
|
|
@@ -32,6 +33,7 @@ export const IOCIdentifier = Object.freeze({
|
|
|
32
33
|
ProcesserExecutorInterface: 'ProcesserExecutorInterface',
|
|
33
34
|
RouteServiceInterface: 'RouteServiceInterface',
|
|
34
35
|
UserServiceInterface: 'UserServiceInterface',
|
|
36
|
+
MessageServiceInterface: 'MessageServiceInterface',
|
|
35
37
|
I18nKeyErrorPlugin: 'I18nKeyErrorPlugin',
|
|
36
38
|
FeApiCommonPlugin: 'FeApiCommonPlugin',
|
|
37
39
|
ApiMockPlugin: 'ApiMockPlugin',
|
|
@@ -74,6 +76,7 @@ export interface IOCIdentifierMap {
|
|
|
74
76
|
[IOCIdentifier.ProcesserExecutorInterface]: ProcesserExecutor;
|
|
75
77
|
[IOCIdentifier.RouteServiceInterface]: RouteService;
|
|
76
78
|
[IOCIdentifier.UserServiceInterface]: UserService;
|
|
79
|
+
[IOCIdentifier.MessageServiceInterface]: MessageService;
|
|
77
80
|
[IOCIdentifier.I18nKeyErrorPlugin]: I18nKeyErrorPlugin;
|
|
78
81
|
[IOCIdentifier.FeApiCommonPlugin]: CorekitBridge.RequestCommonPlugin;
|
|
79
82
|
[IOCIdentifier.ApiMockPlugin]: CorekitBridge.ApiMockPlugin;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Chat send button text
|
|
3
|
+
* @localZh 发送
|
|
4
|
+
* @localEn Send
|
|
5
|
+
*/
|
|
6
|
+
export const COMPONENT_CHAT_SEND = 'component_chat:send';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @description Chat stop button text
|
|
10
|
+
* @localZh 停止
|
|
11
|
+
* @localEn Stop
|
|
12
|
+
*/
|
|
13
|
+
export const COMPONENT_CHAT_STOP = 'component_chat:stop';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @description Chat loading text
|
|
17
|
+
* @localZh 加载中...
|
|
18
|
+
* @localEn Loading...
|
|
19
|
+
*/
|
|
20
|
+
export const COMPONENT_CHAT_LOADING = 'component_chat:loading';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @description Chat input placeholder
|
|
24
|
+
* @localZh 输入消息... (Ctrl+Enter 发送)
|
|
25
|
+
* @localEn Type a message... (Ctrl+Enter to send)
|
|
26
|
+
*/
|
|
27
|
+
export const COMPONENT_CHAT_INPUT_PLACEHOLDER =
|
|
28
|
+
'component_chat:input_placeholder';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @description Chat empty messages
|
|
32
|
+
* @localZh 暂无对话
|
|
33
|
+
* @localEn No messages yet
|
|
34
|
+
*/
|
|
35
|
+
export const COMPONENT_CHAT_EMPTY = 'component_chat:empty';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @description Chat start conversation
|
|
39
|
+
* @localZh 开始对话
|
|
40
|
+
* @localEn Start a conversation
|
|
41
|
+
*/
|
|
42
|
+
export const COMPONENT_CHAT_START = 'component_chat:start';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @description Chat retry button
|
|
46
|
+
* @localZh 重试
|
|
47
|
+
* @localEn Retry
|
|
48
|
+
*/
|
|
49
|
+
export const COMPONENT_CHAT_RETRY = 'component_chat:retry';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @description Chat duration label
|
|
53
|
+
* @localZh 耗时
|
|
54
|
+
* @localEn Duration
|
|
55
|
+
*/
|
|
56
|
+
export const COMPONENT_CHAT_DURATION = 'component_chat:duration';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Message gateway test title
|
|
3
|
+
* @localZh 消息网关测试
|
|
4
|
+
* @localEn Message Gateway Test
|
|
5
|
+
*/
|
|
6
|
+
export const COMPONENT_MESSAGE_BASE_LIST_TITLE =
|
|
7
|
+
'component_message_base_list:title';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @description Message gateway test description
|
|
11
|
+
* @localZh 发送消息并等待网关响应
|
|
12
|
+
* @localEn Send messages and wait for gateway response
|
|
13
|
+
*/
|
|
14
|
+
export const COMPONENT_MESSAGE_BASE_LIST_DESCRIPTION =
|
|
15
|
+
'component_message_base_list:description';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @description No messages yet
|
|
19
|
+
* @localZh 暂无消息
|
|
20
|
+
* @localEn No messages yet
|
|
21
|
+
*/
|
|
22
|
+
export const COMPONENT_MESSAGE_BASE_LIST_NO_MESSAGES =
|
|
23
|
+
'component_message_base_list:no_messages';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @description Send your first message to get started
|
|
27
|
+
* @localZh 发送第一条消息开始使用
|
|
28
|
+
* @localEn Send your first message to get started
|
|
29
|
+
*/
|
|
30
|
+
export const COMPONENT_MESSAGE_BASE_LIST_GET_STARTED =
|
|
31
|
+
'component_message_base_list:get_started';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @description User label
|
|
35
|
+
* @localZh 你
|
|
36
|
+
* @localEn You
|
|
37
|
+
*/
|
|
38
|
+
export const COMPONENT_MESSAGE_BASE_LIST_USER =
|
|
39
|
+
'component_message_base_list:user';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @description Gateway label
|
|
43
|
+
* @localZh 网关
|
|
44
|
+
* @localEn Gateway
|
|
45
|
+
*/
|
|
46
|
+
export const COMPONENT_MESSAGE_BASE_LIST_GATEWAY =
|
|
47
|
+
'component_message_base_list:gateway';
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @description Processing status
|
|
51
|
+
* @localZh 处理中...
|
|
52
|
+
* @localEn Processing...
|
|
53
|
+
*/
|
|
54
|
+
export const COMPONENT_MESSAGE_BASE_LIST_PROCESSING =
|
|
55
|
+
'component_message_base_list:processing';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @description Gateway failed label
|
|
59
|
+
* @localZh 网关(失败)
|
|
60
|
+
* @localEn Gateway (Failed)
|
|
61
|
+
*/
|
|
62
|
+
export const COMPONENT_MESSAGE_BASE_LIST_GATEWAY_FAILED =
|
|
63
|
+
'component_message_base_list:gateway_failed';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @description Failed to send message
|
|
67
|
+
* @localZh 发送消息失败
|
|
68
|
+
* @localEn Failed to send message
|
|
69
|
+
*/
|
|
70
|
+
export const COMPONENT_MESSAGE_BASE_LIST_SEND_FAILED =
|
|
71
|
+
'component_message_base_list:send_failed';
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @description Gateway response label
|
|
75
|
+
* @localZh 网关响应
|
|
76
|
+
* @localEn Gateway Response
|
|
77
|
+
*/
|
|
78
|
+
export const COMPONENT_MESSAGE_BASE_LIST_GATEWAY_RESPONSE =
|
|
79
|
+
'component_message_base_list:gateway_response';
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @description Input placeholder
|
|
83
|
+
* @localZh 在此输入您的消息...
|
|
84
|
+
* @localEn Type your message here...
|
|
85
|
+
*/
|
|
86
|
+
export const COMPONENT_MESSAGE_BASE_LIST_INPUT_PLACEHOLDER =
|
|
87
|
+
'component_message_base_list:input_placeholder';
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @description Send button
|
|
91
|
+
* @localZh 发送
|
|
92
|
+
* @localEn Send
|
|
93
|
+
*/
|
|
94
|
+
export const COMPONENT_MESSAGE_BASE_LIST_SEND_BUTTON =
|
|
95
|
+
'component_message_base_list:send_button';
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @description Error handling tip
|
|
99
|
+
* @localZh 提示:尝试发送 "error" 或 "Failed" 来测试错误处理
|
|
100
|
+
* @localEn Tip: Try sending "error" or "Failed" to test error handling
|
|
101
|
+
*/
|
|
102
|
+
export const COMPONENT_MESSAGE_BASE_LIST_ERROR_TIP =
|
|
103
|
+
'component_message_base_list:error_tip';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Message page title
|
|
3
|
+
* @localZh 消息测试
|
|
4
|
+
* @localEn Message Test
|
|
5
|
+
*/
|
|
6
|
+
export const PAGE_MESSAGE_TITLE = 'page_message:title';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @description Message page description
|
|
10
|
+
* @localZh 消息网关测试页面
|
|
11
|
+
* @localEn Message gateway test page
|
|
12
|
+
*/
|
|
13
|
+
export const PAGE_MESSAGE_DESCRIPTION = 'page_message:description';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @description Message page keywords
|
|
17
|
+
* @localZh 消息, 网关, 测试
|
|
18
|
+
* @localEn Message, Gateway, Test
|
|
19
|
+
*/
|
|
20
|
+
export const PAGE_MESSAGE_KEYWORDS = 'page_message:keywords';
|
|
@@ -5,6 +5,7 @@ import { homeI18n } from './i18n/homeI18n';
|
|
|
5
5
|
import { identifiter18n } from './i18n/identifiter18n';
|
|
6
6
|
import { jsonStorage18n } from './i18n/jsonStorage18n';
|
|
7
7
|
import { login18n } from './i18n/login18n';
|
|
8
|
+
import { messageI18n } from './i18n/messageI18n';
|
|
8
9
|
import { notFound18n, serverError18n } from './i18n/notFoundI18n';
|
|
9
10
|
import { register18n } from './i18n/register18n';
|
|
10
11
|
import { request18n } from './i18n/request18n';
|
|
@@ -88,6 +89,17 @@ export const baseRoutes: RouteConfigValue[] = [
|
|
|
88
89
|
i18nInterface: identifiter18n
|
|
89
90
|
}
|
|
90
91
|
},
|
|
92
|
+
{
|
|
93
|
+
path: 'message',
|
|
94
|
+
element: 'base/MessagePage',
|
|
95
|
+
meta: {
|
|
96
|
+
title: identifier.PAGE_MESSAGE_TITLE,
|
|
97
|
+
description: identifier.PAGE_MESSAGE_DESCRIPTION,
|
|
98
|
+
icon: 'message',
|
|
99
|
+
localNamespace: 'common',
|
|
100
|
+
i18nInterface: messageI18n
|
|
101
|
+
}
|
|
102
|
+
},
|
|
91
103
|
{
|
|
92
104
|
path: '*',
|
|
93
105
|
element: 'NoRouteFound',
|
|
@@ -249,6 +261,17 @@ export const baseNoLocaleRoutes: RouteConfigValue[] = [
|
|
|
249
261
|
localNamespace: 'common',
|
|
250
262
|
i18nInterface: identifiter18n
|
|
251
263
|
}
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
path: 'message',
|
|
267
|
+
element: 'base/MessagePage',
|
|
268
|
+
meta: {
|
|
269
|
+
title: identifier.PAGE_MESSAGE_TITLE,
|
|
270
|
+
description: identifier.PAGE_MESSAGE_DESCRIPTION,
|
|
271
|
+
icon: 'message',
|
|
272
|
+
localNamespace: 'common',
|
|
273
|
+
i18nInterface: messageI18n
|
|
274
|
+
}
|
|
252
275
|
}
|
|
253
276
|
]
|
|
254
277
|
},
|