Merge branch 'dev' into feat/beta
This commit is contained in:
commit
cd906e28f0
2
.github/scripts/rename-app-beta.sh
vendored
2
.github/scripts/rename-app-beta.sh
vendored
@ -17,6 +17,8 @@ fi
|
|||||||
# Use jq to transform the content
|
# Use jq to transform the content
|
||||||
jq '
|
jq '
|
||||||
.name = "jan-beta" |
|
.name = "jan-beta" |
|
||||||
|
.productName = "Jan-beta" |
|
||||||
|
.build.appId = "jan-beta.ai.app" |
|
||||||
.build.productName = "Jan-beta" |
|
.build.productName = "Jan-beta" |
|
||||||
.build.appId = "jan-beta.ai.app" |
|
.build.appId = "jan-beta.ai.app" |
|
||||||
.build.protocols[0].name = "Jan-beta" |
|
.build.protocols[0].name = "Jan-beta" |
|
||||||
|
|||||||
44
.github/workflows/jan-electron-build-beta.yml
vendored
44
.github/workflows/jan-electron-build-beta.yml
vendored
@ -74,8 +74,8 @@ jobs:
|
|||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
beta: true
|
beta: true
|
||||||
|
|
||||||
combine-latest-mac-yml:
|
combine-beta-mac-yml:
|
||||||
needs: [build-macos-x64, build-macos-arm64, create-draft-release]
|
needs: [build-macos-x64, build-macos-arm64, create-draft-release, build-windows-x64, build-linux-x64]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@ -86,26 +86,26 @@ jobs:
|
|||||||
- name: Download mac-x64 artifacts
|
- name: Download mac-x64 artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: latest-mac-x64
|
name: beta-mac-x64
|
||||||
path: ./latest-mac-x64
|
path: ./beta-mac-x64
|
||||||
- name: Download mac-arm artifacts
|
- name: Download mac-arm artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: latest-mac-arm64
|
name: beta-mac-arm64
|
||||||
path: ./latest-mac-arm64
|
path: ./beta-mac-arm64
|
||||||
|
|
||||||
- name: 'Merge latest-mac.yml'
|
- name: 'Merge beta-mac.yml'
|
||||||
# unfortunately electron-builder doesn't understand that we have two different releases for mac-x64 and mac-arm, so we need to manually merge the latest files
|
# unfortunately electron-builder doesn't understand that we have two different releases for mac-x64 and mac-arm, so we need to manually merge the latest files
|
||||||
# see https://github.com/electron-userland/electron-builder/issues/5592
|
# see https://github.com/electron-userland/electron-builder/issues/5592
|
||||||
run: |
|
run: |
|
||||||
ls -la .
|
ls -la .
|
||||||
ls -la ./latest-mac-x64
|
ls -la ./beta-mac-x64
|
||||||
ls -la ./latest-mac-arm64
|
ls -la ./beta-mac-arm64
|
||||||
ls -la ./electron
|
ls -la ./electron
|
||||||
cp ./electron/merge-latest-ymls.js /tmp/merge-latest-ymls.js
|
cp ./electron/merge-latest-ymls.js /tmp/merge-beta-ymls.js
|
||||||
npm install js-yaml --prefix /tmp
|
npm install js-yaml --prefix /tmp
|
||||||
node /tmp/merge-latest-ymls.js ./latest-mac-x64/latest-mac.yml ./latest-mac-arm64/latest-mac.yml ./latest-mac.yml
|
node /tmp/merge-beta-ymls.js ./beta-mac-x64/beta-mac.yml ./beta-mac-arm64/beta-mac.yml ./beta-mac.yml
|
||||||
cat ./latest-mac.yml
|
cat ./beta-mac.yml
|
||||||
|
|
||||||
- name: Yet Another Upload Release Asset Action
|
- name: Yet Another Upload Release Asset Action
|
||||||
uses: shogo82148/actions-upload-release-asset@v1.7.2
|
uses: shogo82148/actions-upload-release-asset@v1.7.2
|
||||||
@ -113,8 +113,24 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ needs.create-draft-release.outputs.upload_url }}
|
upload_url: ${{ needs.create-draft-release.outputs.upload_url }}
|
||||||
asset_path: ./latest-mac.yml
|
asset_path: ./beta-mac.yml
|
||||||
asset_name: latest-mac.yml
|
asset_name: beta-mac.yml
|
||||||
asset_content_type: text/yaml
|
asset_content_type: text/yaml
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
|
- name: Upload beta-mac.yml
|
||||||
|
run: |
|
||||||
|
aws s3 cp ./beta-mac.yml "s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-beta/beta-mac.yml"
|
||||||
|
# sync temp-beta to beta by copy files that are different or new
|
||||||
|
aws s3 sync "s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-beta/" "s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/beta/"
|
||||||
|
env:
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
|
||||||
|
AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }}
|
||||||
|
AWS_EC2_METADATA_DISABLED: "true"
|
||||||
|
|
||||||
|
- name: set release to prerelease
|
||||||
|
run: |
|
||||||
|
gh release edit v${{ needs.create-draft-release.outputs.version }} --draft=false --prerelease
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
16
.github/workflows/template-build-linux-x64.yml
vendored
16
.github/workflows/template-build-linux-x64.yml
vendored
@ -76,6 +76,9 @@ jobs:
|
|||||||
cat ./electron/package.json
|
cat ./electron/package.json
|
||||||
echo "------------------------"
|
echo "------------------------"
|
||||||
cat ./package.json
|
cat ./package.json
|
||||||
|
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "github", "owner": "janhq", "repo": "jan", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
|
||||||
|
mv /tmp/package.json electron/package.json
|
||||||
|
cat electron/package.json
|
||||||
|
|
||||||
- name: Update app version base on tag
|
- name: Update app version base on tag
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
|
||||||
@ -105,7 +108,7 @@ jobs:
|
|||||||
AWS_MAX_ATTEMPTS: "5"
|
AWS_MAX_ATTEMPTS: "5"
|
||||||
|
|
||||||
- name: Build and publish app to github
|
- name: Build and publish app to github
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
|
||||||
run: |
|
run: |
|
||||||
make build-and-publish
|
make build-and-publish
|
||||||
env:
|
env:
|
||||||
@ -113,6 +116,17 @@ jobs:
|
|||||||
ANALYTICS_ID: ${{ secrets.JAN_APP_UMAMI_PROJECT_API_KEY }}
|
ANALYTICS_ID: ${{ secrets.JAN_APP_UMAMI_PROJECT_API_KEY }}
|
||||||
ANALYTICS_HOST: ${{ secrets.JAN_APP_UMAMI_URL }}
|
ANALYTICS_HOST: ${{ secrets.JAN_APP_UMAMI_URL }}
|
||||||
|
|
||||||
|
- name: Build and publish app to github
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true
|
||||||
|
run: |
|
||||||
|
make build-and-publish
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
|
||||||
|
AWS_EC2_METADATA_DISABLED: "true"
|
||||||
|
AWS_MAX_ATTEMPTS: "5"
|
||||||
|
|
||||||
- name: Upload Artifact .deb file
|
- name: Upload Artifact .deb file
|
||||||
if: inputs.public_provider != 'github'
|
if: inputs.public_provider != 'github'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
34
.github/workflows/template-build-macos-arm64.yml
vendored
34
.github/workflows/template-build-macos-arm64.yml
vendored
@ -92,6 +92,9 @@ jobs:
|
|||||||
cat ./electron/package.json
|
cat ./electron/package.json
|
||||||
echo "------------------------"
|
echo "------------------------"
|
||||||
cat ./package.json
|
cat ./package.json
|
||||||
|
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "github", "owner": "janhq", "repo": "jan", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
|
||||||
|
mv /tmp/package.json electron/package.json
|
||||||
|
cat electron/package.json
|
||||||
|
|
||||||
- name: Update app version base on tag
|
- name: Update app version base on tag
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
|
||||||
@ -144,7 +147,7 @@ jobs:
|
|||||||
AWS_MAX_ATTEMPTS: "5"
|
AWS_MAX_ATTEMPTS: "5"
|
||||||
|
|
||||||
- name: Build and publish app to github
|
- name: Build and publish app to github
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
|
||||||
run: |
|
run: |
|
||||||
make build-and-publish
|
make build-and-publish
|
||||||
env:
|
env:
|
||||||
@ -159,6 +162,25 @@ jobs:
|
|||||||
ANALYTICS_ID: ${{ secrets.JAN_APP_UMAMI_PROJECT_API_KEY }}
|
ANALYTICS_ID: ${{ secrets.JAN_APP_UMAMI_PROJECT_API_KEY }}
|
||||||
ANALYTICS_HOST: ${{ secrets.JAN_APP_UMAMI_URL }}
|
ANALYTICS_HOST: ${{ secrets.JAN_APP_UMAMI_URL }}
|
||||||
|
|
||||||
|
- name: Build and publish app to github
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true
|
||||||
|
run: |
|
||||||
|
make build-and-publish
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CSC_LINK: "/tmp/codesign.p12"
|
||||||
|
CSC_KEY_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
|
||||||
|
CSC_IDENTITY_AUTO_DISCOVERY: "true"
|
||||||
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||||
|
APP_PATH: "."
|
||||||
|
DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }}
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
|
||||||
|
AWS_DEFAULT_REGION: auto
|
||||||
|
AWS_EC2_METADATA_DISABLED: "true"
|
||||||
|
AWS_MAX_ATTEMPTS: "5"
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
if: inputs.public_provider != 'github'
|
if: inputs.public_provider != 'github'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@ -167,7 +189,15 @@ jobs:
|
|||||||
path: ./electron/dist/jan-mac-arm64-${{ inputs.new_version }}.dmg
|
path: ./electron/dist/jan-mac-arm64-${{ inputs.new_version }}.dmg
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
|
if: inputs.beta == false
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: latest-mac-arm64
|
name: latest-mac-arm64
|
||||||
path: ./electron/dist/latest-mac.yml
|
path: ./electron/dist/latest-mac.yml
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
if: inputs.beta == true
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: beta-mac-arm64
|
||||||
|
path: ./electron/dist/beta-mac.yml
|
||||||
34
.github/workflows/template-build-macos-x64.yml
vendored
34
.github/workflows/template-build-macos-x64.yml
vendored
@ -92,6 +92,9 @@ jobs:
|
|||||||
cat ./electron/package.json
|
cat ./electron/package.json
|
||||||
echo "------------------------"
|
echo "------------------------"
|
||||||
cat ./package.json
|
cat ./package.json
|
||||||
|
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "github", "owner": "janhq", "repo": "jan", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
|
||||||
|
mv /tmp/package.json electron/package.json
|
||||||
|
cat electron/package.json
|
||||||
|
|
||||||
- name: Update app version base on tag
|
- name: Update app version base on tag
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
|
||||||
@ -144,7 +147,7 @@ jobs:
|
|||||||
AWS_MAX_ATTEMPTS: "5"
|
AWS_MAX_ATTEMPTS: "5"
|
||||||
|
|
||||||
- name: Build and publish app to github
|
- name: Build and publish app to github
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
|
||||||
run: |
|
run: |
|
||||||
make build-and-publish
|
make build-and-publish
|
||||||
env:
|
env:
|
||||||
@ -159,6 +162,25 @@ jobs:
|
|||||||
ANALYTICS_ID: ${{ secrets.JAN_APP_UMAMI_PROJECT_API_KEY }}
|
ANALYTICS_ID: ${{ secrets.JAN_APP_UMAMI_PROJECT_API_KEY }}
|
||||||
ANALYTICS_HOST: ${{ secrets.JAN_APP_UMAMI_URL }}
|
ANALYTICS_HOST: ${{ secrets.JAN_APP_UMAMI_URL }}
|
||||||
|
|
||||||
|
- name: Build and publish app to github
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true
|
||||||
|
run: |
|
||||||
|
make build-and-publish
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CSC_LINK: "/tmp/codesign.p12"
|
||||||
|
CSC_KEY_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
|
||||||
|
CSC_IDENTITY_AUTO_DISCOVERY: "true"
|
||||||
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||||
|
APP_PATH: "."
|
||||||
|
DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }}
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
|
||||||
|
AWS_DEFAULT_REGION: auto
|
||||||
|
AWS_EC2_METADATA_DISABLED: "true"
|
||||||
|
AWS_MAX_ATTEMPTS: "5"
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
if: inputs.public_provider != 'github'
|
if: inputs.public_provider != 'github'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@ -167,7 +189,15 @@ jobs:
|
|||||||
path: ./electron/dist/jan-mac-x64-${{ inputs.new_version }}.dmg
|
path: ./electron/dist/jan-mac-x64-${{ inputs.new_version }}.dmg
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
|
if: inputs.beta == false
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: latest-mac-x64
|
name: latest-mac-x64
|
||||||
path: ./electron/dist/latest-mac.yml
|
path: ./electron/dist/latest-mac.yml
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
if: inputs.beta == true
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: beta-mac-x64
|
||||||
|
path: ./electron/dist/beta-mac.yml
|
||||||
23
.github/workflows/template-build-windows-x64.yml
vendored
23
.github/workflows/template-build-windows-x64.yml
vendored
@ -96,6 +96,9 @@ jobs:
|
|||||||
cat ./package.json
|
cat ./package.json
|
||||||
echo "------------------------"
|
echo "------------------------"
|
||||||
cat ./electron/scripts/uninstaller.nsh
|
cat ./electron/scripts/uninstaller.nsh
|
||||||
|
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "github", "owner": "janhq", "repo": "jan", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
|
||||||
|
mv /tmp/package.json electron/package.json
|
||||||
|
cat electron/package.json
|
||||||
|
|
||||||
- name: Update app version base on tag
|
- name: Update app version base on tag
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
|
||||||
@ -138,7 +141,7 @@ jobs:
|
|||||||
AWS_MAX_ATTEMPTS: "5"
|
AWS_MAX_ATTEMPTS: "5"
|
||||||
|
|
||||||
- name: Build app and publish app to github
|
- name: Build app and publish app to github
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
|
||||||
run: |
|
run: |
|
||||||
make build-and-publish
|
make build-and-publish
|
||||||
env:
|
env:
|
||||||
@ -151,6 +154,24 @@ jobs:
|
|||||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||||
AZURE_CERT_NAME: ${{ secrets.AZURE_CERT_NAME }}
|
AZURE_CERT_NAME: ${{ secrets.AZURE_CERT_NAME }}
|
||||||
|
|
||||||
|
- name: Build app and publish app to github
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true
|
||||||
|
run: |
|
||||||
|
make build-and-publish
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
|
||||||
|
AWS_DEFAULT_REGION: auto
|
||||||
|
AWS_EC2_METADATA_DISABLED: "true"
|
||||||
|
AWS_MAX_ATTEMPTS: "5"
|
||||||
|
AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }}
|
||||||
|
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
|
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||||
|
AZURE_CERT_NAME: ${{ secrets.AZURE_CERT_NAME }}
|
||||||
|
# AZURE_CERT_NAME: homebrewltd
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
if: inputs.public_provider != 'github'
|
if: inputs.public_provider != 'github'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { app } from 'electron'
|
import { app, screen } from 'electron'
|
||||||
import Store from 'electron-store'
|
import Store from 'electron-store'
|
||||||
|
|
||||||
const DEFAULT_WIDTH = 1000
|
const DEFAULT_WIDTH = 1000
|
||||||
@ -22,13 +22,42 @@ export const getBounds = async () => {
|
|||||||
height: DEFAULT_HEIGHT,
|
height: DEFAULT_HEIGHT,
|
||||||
}
|
}
|
||||||
|
|
||||||
const bounds = await storage.get('windowBounds')
|
const bounds = (await storage.get('windowBounds')) as
|
||||||
if (bounds) {
|
| Electron.Rectangle
|
||||||
return bounds as Electron.Rectangle
|
| undefined
|
||||||
} else {
|
|
||||||
|
// If no bounds are saved, use the defaults
|
||||||
|
if (!bounds) {
|
||||||
storage.set('windowBounds', defaultBounds)
|
storage.set('windowBounds', defaultBounds)
|
||||||
return defaultBounds
|
return defaultBounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate that the bounds are on a valid display
|
||||||
|
const displays = screen.getAllDisplays()
|
||||||
|
const isValid = displays.some((display) => {
|
||||||
|
const { x, y, width, height } = display.bounds
|
||||||
|
return (
|
||||||
|
bounds.x >= x &&
|
||||||
|
bounds.x < x + width &&
|
||||||
|
bounds.y >= y &&
|
||||||
|
bounds.y < y + height
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// If the position is valid, return the saved bounds, otherwise return default bounds
|
||||||
|
if (isValid) {
|
||||||
|
return bounds
|
||||||
|
} else {
|
||||||
|
const primaryDisplay = screen.getPrimaryDisplay()
|
||||||
|
const resetBounds = {
|
||||||
|
x: primaryDisplay.bounds.x,
|
||||||
|
y: primaryDisplay.bounds.y,
|
||||||
|
width: DEFAULT_WIDTH,
|
||||||
|
height: DEFAULT_HEIGHT,
|
||||||
|
}
|
||||||
|
storage.set('windowBounds', resetBounds)
|
||||||
|
return resetBounds
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const saveBounds = (bounds: Electron.Rectangle | undefined) => {
|
export const saveBounds = (bounds: Electron.Rectangle | undefined) => {
|
||||||
|
|||||||
@ -13,7 +13,7 @@ fieldset,
|
|||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
color: hsla(var(--modal-fg));
|
color: hsla(var(--modal-fg));
|
||||||
overflow: hidden;
|
overflow: auto;
|
||||||
background-color: hsla(var(--modal-bg));
|
background-color: hsla(var(--modal-bg));
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|||||||
43
web/containers/AutoLink/index.test.tsx
Normal file
43
web/containers/AutoLink/index.test.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import AutoLink from './index'
|
||||||
|
|
||||||
|
describe('AutoLink Component', () => {
|
||||||
|
it('renders text without links correctly', () => {
|
||||||
|
const text = 'This is a test without links.'
|
||||||
|
render(<AutoLink text={text} />)
|
||||||
|
expect(screen.getByText(text)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders text with a single link correctly', () => {
|
||||||
|
const text = 'Check this link: https://example.com'
|
||||||
|
render(<AutoLink text={text} />)
|
||||||
|
const link = screen.getByText('https://example.com')
|
||||||
|
expect(link).toBeInTheDocument()
|
||||||
|
expect(link).toHaveAttribute('href', 'https://example.com')
|
||||||
|
expect(link).toHaveAttribute('target', 'blank')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders text with multiple links correctly', () => {
|
||||||
|
const text = 'Visit https://example.com and http://test.com'
|
||||||
|
render(<AutoLink text={text} />)
|
||||||
|
const link1 = screen.getByText('https://example.com')
|
||||||
|
const link2 = screen.getByText('http://test.com')
|
||||||
|
expect(link1).toBeInTheDocument()
|
||||||
|
expect(link1).toHaveAttribute('href', 'https://example.com')
|
||||||
|
expect(link1).toHaveAttribute('target', 'blank')
|
||||||
|
expect(link2).toBeInTheDocument()
|
||||||
|
expect(link2).toHaveAttribute('href', 'http://test.com')
|
||||||
|
expect(link2).toHaveAttribute('target', 'blank')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders text with a link without protocol correctly', () => {
|
||||||
|
const text = 'Visit example.com for more info.'
|
||||||
|
render(<AutoLink text={text} />)
|
||||||
|
const link = screen.getByText('example.com')
|
||||||
|
expect(link).toBeInTheDocument()
|
||||||
|
expect(link).toHaveAttribute('href', 'http://example.com')
|
||||||
|
expect(link).toHaveAttribute('target', 'blank')
|
||||||
|
})
|
||||||
|
})
|
||||||
38
web/containers/BlankState/index.test.tsx
Normal file
38
web/containers/BlankState/index.test.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import BlankState from './index'
|
||||||
|
|
||||||
|
describe('BlankState Component', () => {
|
||||||
|
it('renders title correctly', () => {
|
||||||
|
const title = 'Test Title'
|
||||||
|
render(<BlankState title={title} />)
|
||||||
|
expect(screen.getByText(title)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders description correctly when provided', () => {
|
||||||
|
const title = 'Test Title'
|
||||||
|
const description = 'Test Description'
|
||||||
|
render(<BlankState title={title} description={description} />)
|
||||||
|
expect(screen.getByText(description)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not render description when not provided', () => {
|
||||||
|
const title = 'Test Title'
|
||||||
|
render(<BlankState title={title} />)
|
||||||
|
expect(screen.queryByText('Test Description')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders action correctly when provided', () => {
|
||||||
|
const title = 'Test Title'
|
||||||
|
const action = <button>Test Action</button>
|
||||||
|
render(<BlankState title={title} action={action} />)
|
||||||
|
expect(screen.getByText('Test Action')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not render action when not provided', () => {
|
||||||
|
const title = 'Test Title'
|
||||||
|
render(<BlankState title={title} />)
|
||||||
|
expect(screen.queryByText('Test Action')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
37
web/containers/Brand/Logo/Mark.test.tsx
Normal file
37
web/containers/Brand/Logo/Mark.test.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import LogoMark from './Mark'
|
||||||
|
|
||||||
|
describe('LogoMark Component', () => {
|
||||||
|
it('renders with default width and height', () => {
|
||||||
|
render(<LogoMark />)
|
||||||
|
const image = screen.getByAltText('Jan - Logo')
|
||||||
|
expect(image).toBeInTheDocument()
|
||||||
|
expect(image).toHaveAttribute('width', '24')
|
||||||
|
expect(image).toHaveAttribute('height', '24')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with provided width and height', () => {
|
||||||
|
render(<LogoMark width={48} height={48} />)
|
||||||
|
const image = screen.getByAltText('Jan - Logo')
|
||||||
|
expect(image).toBeInTheDocument()
|
||||||
|
expect(image).toHaveAttribute('width', '48')
|
||||||
|
expect(image).toHaveAttribute('height', '48')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('applies provided className', () => {
|
||||||
|
render(<LogoMark className="custom-class" />)
|
||||||
|
const image = screen.getByAltText('Jan - Logo')
|
||||||
|
expect(image).toBeInTheDocument()
|
||||||
|
expect(image).toHaveClass('custom-class')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with the correct src and alt attributes', () => {
|
||||||
|
render(<LogoMark />)
|
||||||
|
const image = screen.getByAltText('Jan - Logo')
|
||||||
|
expect(image).toBeInTheDocument()
|
||||||
|
expect(image).toHaveAttribute('src', 'icons/app_icon.svg')
|
||||||
|
expect(image).toHaveAttribute('alt', 'Jan - Logo')
|
||||||
|
})
|
||||||
|
})
|
||||||
56
web/containers/CenterPanelContainer/index.test.tsx
Normal file
56
web/containers/CenterPanelContainer/index.test.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
|
import CenterPanelContainer from './index'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
|
||||||
|
// Mock useAtomValue from jotai
|
||||||
|
jest.mock('jotai', () => ({
|
||||||
|
...jest.requireActual('jotai'),
|
||||||
|
useAtomValue: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('CenterPanelContainer', () => {
|
||||||
|
it('renders with reduceTransparent set to true', () => {
|
||||||
|
// Mock reduceTransparentAtom to be true
|
||||||
|
;(useAtomValue as jest.Mock).mockReturnValue(true)
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CenterPanelContainer>
|
||||||
|
<div>Test Child</div>
|
||||||
|
</CenterPanelContainer>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check that the container renders with no border or rounded corners
|
||||||
|
const container = screen.getByText('Test Child').parentElement
|
||||||
|
expect(container).not.toHaveClass('rounded-lg border')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with reduceTransparent set to false', () => {
|
||||||
|
// Mock reduceTransparentAtom to be false
|
||||||
|
;(useAtomValue as jest.Mock).mockReturnValue(false)
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CenterPanelContainer>
|
||||||
|
<div>Test Child</div>
|
||||||
|
</CenterPanelContainer>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check that the container renders with border and rounded corners
|
||||||
|
const container = screen.getByText('Test Child').parentElement
|
||||||
|
expect(container).toHaveClass('rounded-lg border')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders children correctly', () => {
|
||||||
|
// Mock reduceTransparentAtom to be true for this test
|
||||||
|
;(useAtomValue as jest.Mock).mockReturnValue(true)
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CenterPanelContainer>
|
||||||
|
<div>Child Content</div>
|
||||||
|
</CenterPanelContainer>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify that the child content is rendered
|
||||||
|
expect(screen.getByText('Child Content')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
65
web/containers/CopyInstruction/index.test.tsx
Normal file
65
web/containers/CopyInstruction/index.test.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { render, screen, fireEvent } from '@testing-library/react'
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import CopyOverInstruction from './index'
|
||||||
|
|
||||||
|
// Mock the `useAtom` hook from jotai
|
||||||
|
jest.mock('jotai', () => ({
|
||||||
|
useAtom: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('CopyOverInstruction', () => {
|
||||||
|
const setCopyOverInstructionEnabled = jest.fn()
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
;(useAtom as jest.Mock).mockImplementation(() => [
|
||||||
|
false,
|
||||||
|
setCopyOverInstructionEnabled,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should render the component with the switch in the correct state', () => {
|
||||||
|
render(<CopyOverInstruction />)
|
||||||
|
|
||||||
|
// Assert the text is rendered
|
||||||
|
expect(
|
||||||
|
screen.getByText(/Save instructions for new threads/i)
|
||||||
|
).toBeInTheDocument()
|
||||||
|
|
||||||
|
// Assert the switch is rendered and in the unchecked state
|
||||||
|
const switchInput = screen.getByRole('checkbox')
|
||||||
|
expect(switchInput).toBeInTheDocument()
|
||||||
|
expect(switchInput).not.toBeChecked()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call setCopyOverInstructionEnabled when the switch is toggled', () => {
|
||||||
|
render(<CopyOverInstruction />)
|
||||||
|
|
||||||
|
const switchInput = screen.getByRole('checkbox')
|
||||||
|
|
||||||
|
// Simulate toggling the switch
|
||||||
|
fireEvent.click(switchInput)
|
||||||
|
|
||||||
|
// Assert that the atom setter is called with true when checked
|
||||||
|
expect(setCopyOverInstructionEnabled).toHaveBeenCalledWith(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reflect the updated state when the atom value changes', () => {
|
||||||
|
// Mock the atom to return true (enabled state)
|
||||||
|
;(useAtom as jest.Mock).mockImplementation(() => [
|
||||||
|
true,
|
||||||
|
setCopyOverInstructionEnabled,
|
||||||
|
])
|
||||||
|
|
||||||
|
render(<CopyOverInstruction />)
|
||||||
|
|
||||||
|
const switchInput = screen.getByRole('checkbox')
|
||||||
|
|
||||||
|
// The switch should now be checked
|
||||||
|
expect(switchInput).toBeChecked()
|
||||||
|
})
|
||||||
|
})
|
||||||
115
web/containers/EngineSetting/index.test.tsx
Normal file
115
web/containers/EngineSetting/index.test.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import EngineSetting from './index'
|
||||||
|
import SettingComponentBuilder from '@/containers/ModelSetting/SettingComponent'
|
||||||
|
import { SettingComponentProps } from '@janhq/core'
|
||||||
|
|
||||||
|
// Mock the SettingComponentBuilder component
|
||||||
|
jest.mock('@/containers/ModelSetting/SettingComponent', () =>
|
||||||
|
jest.fn(() => null)
|
||||||
|
)
|
||||||
|
|
||||||
|
describe('EngineSetting', () => {
|
||||||
|
const mockComponentData: SettingComponentProps[] = [
|
||||||
|
{
|
||||||
|
key: 'setting1',
|
||||||
|
title: 'Setting 1',
|
||||||
|
description: 'This is the first setting.',
|
||||||
|
controllerType: 'input',
|
||||||
|
controllerProps: {
|
||||||
|
placeholder: 'Enter text',
|
||||||
|
value: 'default text',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'setting2',
|
||||||
|
title: 'Setting 2',
|
||||||
|
description: 'This is the second setting.',
|
||||||
|
controllerType: 'slider',
|
||||||
|
controllerProps: {
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
step: 1,
|
||||||
|
value: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'setting3',
|
||||||
|
title: 'Setting 3',
|
||||||
|
description: 'This is the third setting.',
|
||||||
|
controllerType: 'checkbox',
|
||||||
|
controllerProps: {
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const onValueChangedMock = jest.fn()
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks() // Clear mocks after each test
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders SettingComponentBuilder with the correct props', () => {
|
||||||
|
render(
|
||||||
|
<EngineSetting
|
||||||
|
componentData={mockComponentData}
|
||||||
|
onValueChanged={onValueChangedMock}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check that SettingComponentBuilder is called with the correct props
|
||||||
|
expect(SettingComponentBuilder).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
componentProps: mockComponentData,
|
||||||
|
disabled: false,
|
||||||
|
onValueUpdated: onValueChangedMock,
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders SettingComponentBuilder with disabled prop', () => {
|
||||||
|
render(
|
||||||
|
<EngineSetting
|
||||||
|
componentData={mockComponentData}
|
||||||
|
onValueChanged={onValueChangedMock}
|
||||||
|
disabled={true}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check that SettingComponentBuilder is called with disabled=true
|
||||||
|
expect(SettingComponentBuilder).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
componentProps: mockComponentData,
|
||||||
|
disabled: true,
|
||||||
|
onValueUpdated: onValueChangedMock,
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls onValueChanged when the value is updated', () => {
|
||||||
|
// Simulating value update in SettingComponentBuilder
|
||||||
|
;(SettingComponentBuilder as jest.Mock).mockImplementation(
|
||||||
|
({ onValueUpdated }) => {
|
||||||
|
// Simulate calling the value update handler
|
||||||
|
onValueUpdated('setting1', 'new value')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
render(
|
||||||
|
<EngineSetting
|
||||||
|
componentData={mockComponentData}
|
||||||
|
onValueChanged={onValueChangedMock}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert that onValueChanged is called with the correct parameters
|
||||||
|
expect(onValueChangedMock).toHaveBeenCalledWith('setting1', 'new value')
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,9 +0,0 @@
|
|||||||
export default function BubbleLoader() {
|
|
||||||
return (
|
|
||||||
<div className="bubble-loader">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
47
web/containers/Loader/ModelStart.test.tsx
Normal file
47
web/containers/Loader/ModelStart.test.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import { render, screen, act } from '@testing-library/react'
|
||||||
|
import ModelStart from './ModelStart' // Adjust the path based on your file structure
|
||||||
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
|
|
||||||
|
// Mock the useActiveModel hook
|
||||||
|
jest.mock('@/hooks/useActiveModel', () => ({
|
||||||
|
useActiveModel: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('ModelStart', () => {
|
||||||
|
const mockSetStateModel = jest.fn()
|
||||||
|
const mockModel = { id: 'test-model' }
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Reset the mock implementation before each test
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders correctly when loading is false', () => {
|
||||||
|
;(useActiveModel as jest.Mock).mockReturnValue({
|
||||||
|
stateModel: {
|
||||||
|
loading: false,
|
||||||
|
state: 'start',
|
||||||
|
model: mockModel,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render(<ModelStart />)
|
||||||
|
// Ensure the component returns null when not loading
|
||||||
|
expect(screen.queryByText(/Starting model/i)).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders loading state with model id', () => {
|
||||||
|
;(useActiveModel as jest.Mock).mockReturnValue({
|
||||||
|
stateModel: {
|
||||||
|
loading: true,
|
||||||
|
state: 'start',
|
||||||
|
model: mockModel,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render(<ModelStart />)
|
||||||
|
// Ensure the loading text is rendered
|
||||||
|
expect(screen.getByText(/Starting model test-model/i)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
47
web/containers/LoadingModal/index.test.tsx
Normal file
47
web/containers/LoadingModal/index.test.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
|
import ResettingModal from './index'
|
||||||
|
|
||||||
|
// Mocking the Jotai atom
|
||||||
|
jest.mock('jotai', () => {
|
||||||
|
const originalModule = jest.requireActual('jotai')
|
||||||
|
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
useAtomValue: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('ResettingModal', () => {
|
||||||
|
it('renders the modal with loading info when provided', () => {
|
||||||
|
const mockLoadingInfo = {
|
||||||
|
title: 'Loading...',
|
||||||
|
message: 'Please wait while we process your request.',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock the useAtomValue hook to return mock loading info
|
||||||
|
;(useAtomValue as jest.Mock).mockReturnValue(mockLoadingInfo)
|
||||||
|
|
||||||
|
const { getByText } = render(<ResettingModal />)
|
||||||
|
|
||||||
|
// Check if the modal title and message are displayed
|
||||||
|
expect(getByText('Loading...')).toBeInTheDocument()
|
||||||
|
expect(
|
||||||
|
getByText('Please wait while we process your request.')
|
||||||
|
).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not render the modal when loading info is undefined', () => {
|
||||||
|
// Mock the useAtomValue hook to return undefined
|
||||||
|
;(useAtomValue as jest.Mock).mockReturnValue(undefined)
|
||||||
|
|
||||||
|
const { queryByText } = render(<ResettingModal />)
|
||||||
|
|
||||||
|
// Check that the modal does not appear
|
||||||
|
expect(queryByText('Loading...')).not.toBeInTheDocument()
|
||||||
|
expect(
|
||||||
|
queryByText('Please wait while we process your request.')
|
||||||
|
).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
56
web/containers/MainViewContainer/index.test.tsx
Normal file
56
web/containers/MainViewContainer/index.test.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import '@testing-library/jest-dom'
|
||||||
|
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
|
import MainViewContainer from './index'
|
||||||
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
|
// Mocking the Jotai atom
|
||||||
|
jest.mock('jotai', () => {
|
||||||
|
const originalModule = jest.requireActual('jotai')
|
||||||
|
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
useAtomValue: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Mocking the screen components
|
||||||
|
jest.mock('@/screens/Hub', () => () => <div>Hub Screen</div>)
|
||||||
|
jest.mock('@/screens/LocalServer', () => () => <div>Local Server Screen</div>)
|
||||||
|
jest.mock('@/screens/Settings', () => () => <div>Settings Screen</div>)
|
||||||
|
jest.mock('@/screens/Thread', () => () => <div>Thread Screen</div>)
|
||||||
|
|
||||||
|
describe('MainViewContainer', () => {
|
||||||
|
it('renders HubScreen when mainViewState is Hub', () => {
|
||||||
|
;(useAtomValue as jest.Mock).mockReturnValue(MainViewState.Hub)
|
||||||
|
|
||||||
|
const { getByText } = render(<MainViewContainer />)
|
||||||
|
|
||||||
|
expect(getByText('Hub Screen')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders SettingsScreen when mainViewState is Settings', () => {
|
||||||
|
;(useAtomValue as jest.Mock).mockReturnValue(MainViewState.Settings)
|
||||||
|
|
||||||
|
const { getByText } = render(<MainViewContainer />)
|
||||||
|
|
||||||
|
expect(getByText('Settings Screen')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders LocalServerScreen when mainViewState is LocalServer', () => {
|
||||||
|
;(useAtomValue as jest.Mock).mockReturnValue(MainViewState.LocalServer)
|
||||||
|
|
||||||
|
const { getByText } = render(<MainViewContainer />)
|
||||||
|
|
||||||
|
expect(getByText('Local Server Screen')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders ThreadScreen when mainViewState is not defined', () => {
|
||||||
|
;(useAtomValue as jest.Mock).mockReturnValue(undefined)
|
||||||
|
|
||||||
|
const { getByText } = render(<MainViewContainer />)
|
||||||
|
|
||||||
|
expect(getByText('Thread Screen')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
85
web/containers/ModelConfigInput/index.test.tsx
Normal file
85
web/containers/ModelConfigInput/index.test.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent } from '@testing-library/react'
|
||||||
|
import ModelConfigInput from './index'
|
||||||
|
import { Tooltip } from '@janhq/joi'
|
||||||
|
|
||||||
|
// Mocking the Tooltip component to simplify testing
|
||||||
|
jest.mock('@janhq/joi', () => ({
|
||||||
|
...jest.requireActual('@janhq/joi'),
|
||||||
|
Tooltip: ({
|
||||||
|
trigger,
|
||||||
|
content,
|
||||||
|
}: {
|
||||||
|
trigger: React.ReactNode
|
||||||
|
content: string
|
||||||
|
}) => (
|
||||||
|
<div data-testid="tooltip">
|
||||||
|
{trigger}
|
||||||
|
<span>{content}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('ModelConfigInput', () => {
|
||||||
|
it('renders correctly with given props', () => {
|
||||||
|
const { getByText, getByPlaceholderText } = render(
|
||||||
|
<ModelConfigInput
|
||||||
|
title="Test Title"
|
||||||
|
description="This is a description."
|
||||||
|
placeholder="Enter text here"
|
||||||
|
value=""
|
||||||
|
name={''}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if title is rendered
|
||||||
|
expect(getByText('Test Title')).toBeInTheDocument()
|
||||||
|
|
||||||
|
// Check if the description tooltip content is rendered
|
||||||
|
expect(getByText('This is a description.')).toBeInTheDocument()
|
||||||
|
|
||||||
|
// Check if the placeholder is rendered
|
||||||
|
expect(getByPlaceholderText('Enter text here')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls onValueChanged when value changes', () => {
|
||||||
|
const onValueChangedMock = jest.fn()
|
||||||
|
const { getByPlaceholderText } = render(
|
||||||
|
<ModelConfigInput
|
||||||
|
title="Test Title"
|
||||||
|
description="This is a description."
|
||||||
|
placeholder="Enter text here"
|
||||||
|
value=""
|
||||||
|
onValueChanged={onValueChangedMock}
|
||||||
|
name={''}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
const textArea = getByPlaceholderText('Enter text here')
|
||||||
|
|
||||||
|
// Simulate typing in the textarea
|
||||||
|
fireEvent.change(textArea, { target: { value: 'New Value' } })
|
||||||
|
|
||||||
|
// Check if onValueChanged was called with the new value
|
||||||
|
expect(onValueChangedMock).toHaveBeenCalledWith('New Value')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('disables the textarea when disabled prop is true', () => {
|
||||||
|
const { getByPlaceholderText } = render(
|
||||||
|
<ModelConfigInput
|
||||||
|
title="Test Title"
|
||||||
|
description="This is a description."
|
||||||
|
placeholder="Enter text here"
|
||||||
|
value=""
|
||||||
|
disabled={true}
|
||||||
|
name={''}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
const textArea = getByPlaceholderText('Enter text here')
|
||||||
|
|
||||||
|
// Check if the textarea is disabled
|
||||||
|
expect(textArea).toBeDisabled()
|
||||||
|
})
|
||||||
|
})
|
||||||
110
web/containers/Providers/Responsive.test.tsx
Normal file
110
web/containers/Providers/Responsive.test.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import React from 'react'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
|
import Responsive from './Responsive'
|
||||||
|
import { showLeftPanelAtom, showRightPanelAtom } from '@/helpers/atoms/App.atom'
|
||||||
|
|
||||||
|
// Mocking the required atoms
|
||||||
|
jest.mock('jotai', () => {
|
||||||
|
const originalModule = jest.requireActual('jotai')
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
useAtom: jest.fn(),
|
||||||
|
useAtomValue: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockSetShowLeftPanel = jest.fn()
|
||||||
|
const mockSetShowRightPanel = jest.fn()
|
||||||
|
const mockShowLeftPanel = true
|
||||||
|
const mockShowRightPanel = true
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mocking the atom behavior
|
||||||
|
;(useAtom as jest.Mock).mockImplementation((atom) => {
|
||||||
|
if (atom === showLeftPanelAtom) {
|
||||||
|
return [mockShowLeftPanel, mockSetShowLeftPanel]
|
||||||
|
}
|
||||||
|
if (atom === showRightPanelAtom) {
|
||||||
|
return [mockShowRightPanel, mockSetShowRightPanel]
|
||||||
|
}
|
||||||
|
return [null, jest.fn()]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Responsive', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
// Mocking the window.matchMedia function
|
||||||
|
window.matchMedia = jest.fn().mockImplementation((query) => {
|
||||||
|
return {
|
||||||
|
matches: false, // Set this to true to simulate mobile view
|
||||||
|
addListener: jest.fn(),
|
||||||
|
removeListener: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders children correctly', () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<Responsive>
|
||||||
|
<div>Child Content</div>
|
||||||
|
</Responsive>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if the child content is rendered
|
||||||
|
expect(getByText('Child Content')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('hides left and right panels on small screens', () => {
|
||||||
|
// Simulate mobile view
|
||||||
|
window.matchMedia = jest.fn().mockImplementation((query) => ({
|
||||||
|
matches: true, // Change to true to simulate mobile
|
||||||
|
addListener: jest.fn(),
|
||||||
|
removeListener: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Responsive>
|
||||||
|
<div>Child Content</div>
|
||||||
|
</Responsive>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check that the left and right panel states were updated to false
|
||||||
|
expect(mockSetShowLeftPanel).toHaveBeenCalledWith(false)
|
||||||
|
expect(mockSetShowRightPanel).toHaveBeenCalledWith(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('restores the last known panel states on larger screens', () => {
|
||||||
|
// Simulate mobile view first
|
||||||
|
window.matchMedia = jest.fn().mockImplementation((query) => ({
|
||||||
|
matches: true, // Change to true to simulate mobile
|
||||||
|
addListener: jest.fn(),
|
||||||
|
removeListener: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Responsive>
|
||||||
|
<div>Child Content</div>
|
||||||
|
</Responsive>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Change back to desktop view
|
||||||
|
window.matchMedia = jest.fn().mockImplementation((query) => ({
|
||||||
|
matches: false, // Change to false to simulate desktop
|
||||||
|
addListener: jest.fn(),
|
||||||
|
removeListener: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Call the effect manually to simulate the component re-rendering
|
||||||
|
const rerender = render(
|
||||||
|
<Responsive>
|
||||||
|
<div>Child Content</div>
|
||||||
|
</Responsive>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check that the last known states were restored (which were true initially)
|
||||||
|
expect(mockSetShowLeftPanel).toHaveBeenCalledWith(true)
|
||||||
|
expect(mockSetShowRightPanel).toHaveBeenCalledWith(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
24
web/containers/Providers/Theme.test.tsx
Normal file
24
web/containers/Providers/Theme.test.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import React from 'react'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import ThemeWrapper from './Theme'
|
||||||
|
|
||||||
|
// Mock the ThemeProvider from next-themes
|
||||||
|
jest.mock('next-themes', () => ({
|
||||||
|
ThemeProvider: ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<div>{children}</div>
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('ThemeWrapper', () => {
|
||||||
|
it('renders children within ThemeProvider', () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<ThemeWrapper>
|
||||||
|
<div>Child Component</div>
|
||||||
|
</ThemeWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if the child component is rendered
|
||||||
|
expect(getByText('Child Component')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
126
web/containers/RightPanelContainer/index.test.tsx
Normal file
126
web/containers/RightPanelContainer/index.test.tsx
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import '@testing-library/jest-dom'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent } from '@testing-library/react'
|
||||||
|
import RightPanelContainer from './index'
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
|
|
||||||
|
// Mocking ResizeObserver
|
||||||
|
class ResizeObserver {
|
||||||
|
observe() {}
|
||||||
|
unobserve() {}
|
||||||
|
disconnect() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.ResizeObserver = ResizeObserver
|
||||||
|
|
||||||
|
// Mocking window.matchMedia
|
||||||
|
Object.defineProperty(window, 'matchMedia', {
|
||||||
|
writable: true,
|
||||||
|
value: jest.fn().mockImplementation((query) => ({
|
||||||
|
matches: false,
|
||||||
|
media: query,
|
||||||
|
onchange: null,
|
||||||
|
addListener: jest.fn(), // deprecated
|
||||||
|
removeListener: jest.fn(), // deprecated
|
||||||
|
addEventListener: jest.fn(),
|
||||||
|
removeEventListener: jest.fn(),
|
||||||
|
dispatchEvent: jest.fn(),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Mocking the required atoms
|
||||||
|
jest.mock('jotai', () => {
|
||||||
|
const originalModule = jest.requireActual('jotai')
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
useAtom: jest.fn(),
|
||||||
|
useAtomValue: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockSetShowRightPanel = jest.fn()
|
||||||
|
const mockShowRightPanel = true // Change this to test the panel visibility
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Setting up the localStorage mock
|
||||||
|
localStorage.clear()
|
||||||
|
localStorage.setItem('rightPanelWidth', '280') // Setting a default width
|
||||||
|
|
||||||
|
// Mocking the atom behavior
|
||||||
|
;(useAtom as jest.Mock).mockImplementation(() => [
|
||||||
|
mockShowRightPanel,
|
||||||
|
mockSetShowRightPanel,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('RightPanelContainer', () => {
|
||||||
|
it('renders correctly with children', () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<RightPanelContainer>
|
||||||
|
<div>Child Content</div>
|
||||||
|
</RightPanelContainer>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if the child content is rendered
|
||||||
|
expect(getByText('Child Content')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('initializes width from localStorage', () => {
|
||||||
|
const { container } = render(<RightPanelContainer />)
|
||||||
|
|
||||||
|
// Check the width from localStorage is applied
|
||||||
|
const rightPanel = container.firstChild as HTMLDivElement
|
||||||
|
expect(rightPanel.style.width).toBe('280px') // Width from localStorage
|
||||||
|
})
|
||||||
|
|
||||||
|
it('changes width on resizing', () => {
|
||||||
|
const { container } = render(<RightPanelContainer />)
|
||||||
|
|
||||||
|
const rightPanel = container.firstChild as HTMLDivElement
|
||||||
|
|
||||||
|
// Simulate mouse down on the resize handle
|
||||||
|
const resizeHandle = document.createElement('div')
|
||||||
|
resizeHandle.className = 'group/resize'
|
||||||
|
rightPanel.appendChild(resizeHandle)
|
||||||
|
|
||||||
|
// Simulate mouse down to start resizing
|
||||||
|
fireEvent.mouseDown(resizeHandle)
|
||||||
|
|
||||||
|
// Simulate mouse move event
|
||||||
|
fireEvent.mouseMove(window, { clientX: 100 })
|
||||||
|
|
||||||
|
// Simulate mouse up to stop resizing
|
||||||
|
fireEvent.mouseUp(window)
|
||||||
|
|
||||||
|
// Verify that the right panel's width changes
|
||||||
|
// Since we can't get the actual width calculation in this test,
|
||||||
|
// you may want to check if the rightPanelWidth is updated in your implementation.
|
||||||
|
// Here, just check if the function is called:
|
||||||
|
expect(localStorage.getItem('rightPanelWidth')).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('hides panel when clicked outside on mobile', () => {
|
||||||
|
// Mock useMediaQuery to simulate mobile view
|
||||||
|
;(window.matchMedia as jest.Mock).mockImplementation((query) => ({
|
||||||
|
matches: true, // Always return true for mobile
|
||||||
|
addListener: jest.fn(),
|
||||||
|
removeListener: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<RightPanelContainer>
|
||||||
|
<div>Child Content</div>
|
||||||
|
</RightPanelContainer>
|
||||||
|
)
|
||||||
|
|
||||||
|
const rightPanel = container.firstChild as HTMLDivElement
|
||||||
|
|
||||||
|
// Simulate a click outside
|
||||||
|
fireEvent.mouseDown(document.body)
|
||||||
|
fireEvent.mouseUp(document.body) // Ensure the click event is completed
|
||||||
|
|
||||||
|
// Verify that setShowRightPanel was called to hide the panel
|
||||||
|
expect(mockSetShowRightPanel).toHaveBeenCalledWith(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -19,8 +19,8 @@ const config = {
|
|||||||
runner: './testRunner.js',
|
runner: './testRunner.js',
|
||||||
collectCoverageFrom: ['./**/*.{ts,tsx}'],
|
collectCoverageFrom: ['./**/*.{ts,tsx}'],
|
||||||
transform: {
|
transform: {
|
||||||
"^.+\\.tsx?$": [
|
'^.+\\.tsx?$': [
|
||||||
"ts-jest",
|
'ts-jest',
|
||||||
{
|
{
|
||||||
diagnostics: false,
|
diagnostics: false,
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user