Compare commits
36 Commits
kurt-rebas
...
v0.24.13
Author | SHA1 | Date | |
---|---|---|---|
f441998f1a | |||
531496420e | |||
13bb482904 | |||
562959ee22 | |||
b044f6faef | |||
d916c79874 | |||
4fd5e26abe | |||
8f9bef922f | |||
545e610bbc | |||
55a3e2a4ed | |||
591f17b182 | |||
a7a88bd762 | |||
0916f990cb | |||
75ae4b4a4a | |||
4a490d5900 | |||
4d9cdc6b40 | |||
0d3880233c | |||
8a029605bd | |||
f26adee360 | |||
0f2a01b6c8 | |||
e099c95c5f | |||
f23bc673aa | |||
b60c1e874d | |||
5857684ebc | |||
e8fc6bc037 | |||
5bdd090119 | |||
669cab8737 | |||
f1ea60d6ab | |||
3faec650b1 | |||
b2b62ec163 | |||
5b798c2aa3 | |||
a23bd1f034 | |||
4d00dddfd8 | |||
f055acb6a6 | |||
bf9d88e9a5 | |||
712a3790e8 |
15
.github/workflows/ci.yml
vendored
@ -4,7 +4,7 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- tauri
|
||||
release:
|
||||
types: [published]
|
||||
schedule:
|
||||
@ -123,7 +123,7 @@ jobs:
|
||||
git commit -am "Look at this (photo)Graph *in the voice of Nickelback*" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -412,17 +412,6 @@ jobs:
|
||||
E2E_APPLICATION: "./src-tauri/target/${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}/zoo-modeling-app"
|
||||
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
|
||||
- name: Run e2e tests (windows only)
|
||||
if: ${{ matrix.os == 'windows-latest' && github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: |
|
||||
cargo install tauri-driver --force
|
||||
yarn wdio run wdio.conf.ts
|
||||
env:
|
||||
E2E_APPLICATION: ".\\src-tauri\\target\\${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}\\Zoo Modeling App.exe"
|
||||
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_API_BASE_URL: ${{ env.BUILD_RELEASE == 'true' && 'https://api.zoo.dev' || 'https://api.dev.zoo.dev' }}
|
||||
E2E_TAURI_ENABLED: true
|
||||
TS_NODE_COMPILER_OPTIONS: '{"module": "commonjs"}'
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
|
2
.github/workflows/create-release.yml
vendored
@ -3,7 +3,7 @@ name: Create Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- tauri
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
|
@ -117,6 +117,7 @@ Which commands from setup are one off vs need to be run every time?
|
||||
The following will need to be run when checking out a new commit and guarantees the build is not stale:
|
||||
```bash
|
||||
yarn install
|
||||
yarn wasm-prep
|
||||
yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build
|
||||
yarn start # or yarn build:local && yarn serve for slower but more production-like build
|
||||
```
|
||||
|
@ -25,5 +25,5 @@ once fixed in engine will just start working here with no language changes.
|
||||
|
||||
Sketching on the chamfered face does not currently work.
|
||||
|
||||
- **Shell**: Shell is only working for `end` faces, not for `side` or `start`
|
||||
faces. We are tracking the engine side bug on this.
|
||||
- **Shell**: Shell sometimes does not work when arcs or fillets are involved.
|
||||
We are tracking the engine side bug on this.
|
||||
|
@ -197582,7 +197582,9 @@
|
||||
"unpublished": false,
|
||||
"deprecated": false,
|
||||
"examples": [
|
||||
"const firstSketch = startSketchOn('XY')\n |> startProfileAt([-12, 12], %)\n |> line([24, 0], %)\n |> line([0, -24], %)\n |> line([-24, 0], %)\n |> close(%)\n |> extrude(6, %)\n\n// Remove the end face for the extrusion.\nshell({ faces: ['end'], thickness: 0.25 }, firstSketch)"
|
||||
"const firstSketch = startSketchOn('XY')\n |> startProfileAt([-12, 12], %)\n |> line([24, 0], %)\n |> line([0, -24], %)\n |> line([-24, 0], %)\n |> close(%)\n |> extrude(6, %)\n\n// Remove the end face for the extrusion.\nshell({ faces: ['end'], thickness: 0.25 }, firstSketch)",
|
||||
"const firstSketch = startSketchOn('-XZ')\n |> startProfileAt([-12, 12], %)\n |> line([24, 0], %)\n |> line([0, -24], %)\n |> line([-24, 0], %)\n |> close(%)\n |> extrude(6, %)\n\n// Remove the start face for the extrusion.\nshell({ faces: ['start'], thickness: 0.25 }, firstSketch)",
|
||||
"const firstSketch = startSketchOn('XY')\n |> startProfileAt([-12, 12], %)\n |> line([24, 0], %)\n |> line([0, -24], %)\n |> line([-24, 0], %, $myTag)\n |> close(%)\n |> extrude(6, %)\n\n// Remove a tagged face for the extrusion.\nshell({ faces: [myTag], thickness: 0.25 }, firstSketch)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 49 KiB |
@ -1,8 +1,12 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import {
|
||||
PLAYWRIGHT_MOCK_EXPORT_DURATION,
|
||||
PLAYWRIGHT_TOAST_DURATION,
|
||||
} from 'lib/constants'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
@ -250,7 +254,7 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
await expect(exportButton).toBeVisible()
|
||||
|
||||
// Click the export button
|
||||
exportButton.click()
|
||||
await exportButton.click()
|
||||
|
||||
// Click the stl.
|
||||
const stlOption = page.getByText('glTF')
|
||||
@ -279,7 +283,7 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
|
||||
// Click the code editor
|
||||
page.locator('.cm-content').click()
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
@ -288,8 +292,7 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
// Now add in code that works.
|
||||
page.locator('.cm-content').fill(bracket)
|
||||
page.locator('.cm-content').click()
|
||||
await page.locator('.cm-content').fill(bracket)
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
@ -302,7 +305,7 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
// Now try exporting
|
||||
|
||||
// Click the export button
|
||||
exportButton.click()
|
||||
await exportButton.click()
|
||||
|
||||
// Click the stl.
|
||||
await expect(stlOption).toBeVisible()
|
||||
@ -330,32 +333,101 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async (code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, bracket)
|
||||
await test.step('Set up the code and durations', async () => {
|
||||
await page.addInitScript(
|
||||
async ({ code, toastDurationKey, exportDurationKey }) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
// Normally we make these durations short to speed up PW tests
|
||||
// to superhuman speeds. But in this case we want to make sure
|
||||
// the export toast is visible for a while, and the export
|
||||
// duration is long enough to make sure the export toast is visible
|
||||
localStorage.setItem(toastDurationKey, '1500')
|
||||
localStorage.setItem(exportDurationKey, '750')
|
||||
},
|
||||
{
|
||||
code: bracket,
|
||||
toastDurationKey: PLAYWRIGHT_TOAST_DURATION,
|
||||
exportDurationKey: PLAYWRIGHT_MOCK_EXPORT_DURATION,
|
||||
}
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// expect zero errors in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
// expect zero errors in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
|
||||
await test.step('Blocked second export', async () => {
|
||||
await clickExportButton(page)
|
||||
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
|
||||
await clickExportButton(page)
|
||||
|
||||
await test.step('The second export is blocked', async () => {
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
await expect(alreadyExportingToastMessage).toBeVisible()
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
})
|
||||
|
||||
await test.step('The first export still succeeds', async () => {
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
await expect(errorToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
|
||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('Successful, unblocked export', async () => {
|
||||
// Try exporting again.
|
||||
await clickExportButton(page)
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
|
||||
// Expect it to succeed.
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
await expect(errorToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
||||
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
async function clickExportButton(page: Page) {
|
||||
await test.step('Running export flow', async () => {
|
||||
// export the model
|
||||
const exportButton = page.getByTestId('export-pane-button')
|
||||
await expect(exportButton).toBeVisible()
|
||||
await expect(exportButton).toBeEnabled()
|
||||
|
||||
// Click the export button
|
||||
exportButton.click()
|
||||
await exportButton.click()
|
||||
|
||||
// Click the stl.
|
||||
const stlOption = page.getByText('glTF')
|
||||
await expect(stlOption).toBeVisible()
|
||||
const gltfOption = page.getByRole('option', { name: 'glTF' })
|
||||
await expect(gltfOption).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
@ -364,71 +436,5 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
await expect(submitButton).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
|
||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||
|
||||
// Try exporting again.
|
||||
// Click the export button
|
||||
exportButton.click()
|
||||
|
||||
// Click the stl.
|
||||
await expect(stlOption).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Click the checkbox
|
||||
await expect(submitButton).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
await expect(alreadyExportingToastMessage).toBeVisible()
|
||||
|
||||
// Expect it to succeed.
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
await expect(errorToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
|
||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
||||
|
||||
// Try exporting again.
|
||||
// Click the export button
|
||||
exportButton.click()
|
||||
|
||||
// Click the stl.
|
||||
await expect(stlOption).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Click the checkbox
|
||||
await expect(submitButton).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
|
||||
// Expect it to succeed.
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
await expect(errorToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
||||
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import pixelMatch from 'pixelmatch'
|
||||
import { PNG } from 'pngjs'
|
||||
import { Protocol } from 'playwright-core/types/protocol'
|
||||
import type { Models } from '@kittycad/lib'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
import { APP_NAME, COOKIE_NAME } from 'lib/constants'
|
||||
import waitOn from 'wait-on'
|
||||
import { secrets } from './secrets'
|
||||
import { TEST_SETTINGS_KEY, TEST_SETTINGS } from './storageStates'
|
||||
@ -643,6 +643,16 @@ export async function setup(context: BrowserContext, page: Page) {
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS }),
|
||||
}
|
||||
)
|
||||
|
||||
await context.addCookies([
|
||||
{
|
||||
name: COOKIE_NAME,
|
||||
value: secrets.token,
|
||||
path: '/',
|
||||
domain: 'localhost',
|
||||
secure: true,
|
||||
},
|
||||
])
|
||||
// kill animations, speeds up tests and reduced flakiness
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||
}
|
||||
|
669
e2e/playwright/text-to-cad-tests.spec.ts
Normal file
@ -0,0 +1,669 @@
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
test.describe('Text-to-CAD tests', () => {
|
||||
test('basic lego happy case', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const submittingToastMessage = page.getByText(
|
||||
`Submitting to Text-to-CAD API...`
|
||||
)
|
||||
await expect(submittingToastMessage).toBeVisible()
|
||||
|
||||
const generatingToastMessage = page.getByText(
|
||||
`Generating parametric model...`
|
||||
)
|
||||
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||
|
||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||
|
||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||
|
||||
// Hit copy to clipboard.
|
||||
const copyToClipboardButton = page.getByRole('button', {
|
||||
name: 'Copy to clipboard',
|
||||
})
|
||||
await expect(copyToClipboardButton).toBeVisible()
|
||||
|
||||
await copyToClipboardButton.click()
|
||||
|
||||
// Expect the code to be copied.
|
||||
await expect(page.getByText('Copied')).toBeVisible()
|
||||
|
||||
// Click in the code editor.
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
// Paste the code.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.press('KeyV')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
|
||||
// Expect the code to be pasted.
|
||||
await expect(page.locator('.cm-content')).toContainText(`const`)
|
||||
|
||||
// make sure a model renders.
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Find the toast close button.
|
||||
const closeButton = page.getByRole('button', { name: 'Close' })
|
||||
await expect(closeButton).toBeVisible()
|
||||
await closeButton.click()
|
||||
|
||||
// The toast should disappear.
|
||||
await expect(successToastMessage).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('success model, then ignore success toast, user can create new prompt from command bar', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await sendPromptFromCommandBar(page, 'a 2x6 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const submittingToastMessage = page.getByText(
|
||||
`Submitting to Text-to-CAD API...`
|
||||
)
|
||||
await expect(submittingToastMessage).toBeVisible()
|
||||
|
||||
const generatingToastMessage = page.getByText(
|
||||
`Generating parametric model...`
|
||||
)
|
||||
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||
|
||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||
|
||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
|
||||
// Can send a new prompt from the command bar.
|
||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(submittingToastMessage).toBeVisible()
|
||||
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||
|
||||
// Expect 2 success toasts.
|
||||
await expect(successToastMessage).toHaveCount(2, {
|
||||
timeout: 15000,
|
||||
})
|
||||
await expect(page.getByText('a 2x4 lego')).toBeVisible()
|
||||
await expect(page.getByText('a 2x6 lego')).toBeVisible()
|
||||
})
|
||||
|
||||
test('you can reject text-to-cad output and it does nothing', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const submittingToastMessage = page.getByText(
|
||||
`Submitting to Text-to-CAD API...`
|
||||
)
|
||||
await expect(submittingToastMessage).toBeVisible()
|
||||
|
||||
const generatingToastMessage = page.getByText(
|
||||
`Generating parametric model...`
|
||||
)
|
||||
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||
|
||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||
|
||||
// Hit copy to clipboard.
|
||||
const rejectButton = page.getByRole('button', { name: 'Reject' })
|
||||
await expect(rejectButton).toBeVisible()
|
||||
|
||||
await rejectButton.click()
|
||||
|
||||
// The toast should disappear.
|
||||
await expect(successToastMessage).not.toBeVisible()
|
||||
|
||||
// Expect no code.
|
||||
await expect(page.locator('.cm-content')).toContainText(``)
|
||||
})
|
||||
|
||||
test('sending a bad prompt fails, can dismiss', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandBarButton).toBeVisible()
|
||||
// Click the command bar button
|
||||
await commandBarButton.click()
|
||||
|
||||
// Wait for the command bar to appear
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
const textToCadCommand = page.getByText('Text-to-CAD')
|
||||
await expect(textToCadCommand.first()).toBeVisible()
|
||||
// Click the Text-to-CAD command
|
||||
await textToCadCommand.first().click()
|
||||
|
||||
// Enter the prompt.
|
||||
const prompt = page.getByText('Prompt')
|
||||
await expect(prompt.first()).toBeVisible()
|
||||
|
||||
// Type the prompt.
|
||||
await page.keyboard.type(
|
||||
'akjsndladf lajbhflauweyfa;wieufjn;wieJNUF;.wjdfn weh Fwhefb'
|
||||
)
|
||||
await page.waitForTimeout(1000)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const submittingToastMessage = page.getByText(
|
||||
`Submitting to Text-to-CAD API...`
|
||||
)
|
||||
await expect(submittingToastMessage).toBeVisible()
|
||||
|
||||
const generatingToastMessage = page.getByText(
|
||||
`Generating parametric model...`
|
||||
)
|
||||
await expect(generatingToastMessage).toBeVisible()
|
||||
|
||||
const failureToastMessage = page.getByText(
|
||||
`The prompt must clearly describe a CAD model`
|
||||
)
|
||||
await expect(failureToastMessage).toBeVisible()
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
// Make sure the toast did not say it was successful.
|
||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||
await expect(successToastMessage).not.toBeVisible()
|
||||
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||
|
||||
// Find the toast dismiss button.
|
||||
const dismissButton = page.getByRole('button', { name: 'Dismiss' })
|
||||
await expect(dismissButton).toBeVisible()
|
||||
await dismissButton.click()
|
||||
|
||||
// The toast should disappear.
|
||||
await expect(failureToastMessage).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('sending a bad prompt fails, can start over from toast', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandBarButton).toBeVisible()
|
||||
// Click the command bar button
|
||||
await commandBarButton.click()
|
||||
|
||||
// Wait for the command bar to appear
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
const textToCadCommand = page.getByText('Text-to-CAD')
|
||||
await expect(textToCadCommand.first()).toBeVisible()
|
||||
// Click the Text-to-CAD command
|
||||
await textToCadCommand.first().click()
|
||||
|
||||
// Enter the prompt.
|
||||
const prompt = page.getByText('Prompt')
|
||||
await expect(prompt.first()).toBeVisible()
|
||||
|
||||
const badPrompt =
|
||||
'akjsndladf lajbhflauweyfa;wieufjn;wieJNUF;.wjdfn weh Fwhefb'
|
||||
|
||||
// Type the prompt.
|
||||
await page.keyboard.type(badPrompt)
|
||||
await page.waitForTimeout(1000)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const submittingToastMessage = page.getByText(
|
||||
`Submitting to Text-to-CAD API...`
|
||||
)
|
||||
await expect(submittingToastMessage).toBeVisible()
|
||||
|
||||
const generatingToastMessage = page.getByText(
|
||||
`Generating parametric model...`
|
||||
)
|
||||
await expect(generatingToastMessage).toBeVisible()
|
||||
|
||||
const failureToastMessage = page.getByText(
|
||||
`The prompt must clearly describe a CAD model`
|
||||
)
|
||||
await expect(failureToastMessage).toBeVisible()
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
// Make sure the toast did not say it was successful.
|
||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||
await expect(successToastMessage).not.toBeVisible()
|
||||
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||
|
||||
// Click the edit prompt button to try again.
|
||||
const editPromptButton = page.getByRole('button', { name: 'Edit prompt' })
|
||||
await expect(editPromptButton).toBeVisible()
|
||||
await editPromptButton.click()
|
||||
|
||||
// The toast should disappear.
|
||||
await expect(failureToastMessage).not.toBeVisible()
|
||||
|
||||
// Make sure the old prompt is still there and can be edited.
|
||||
await expect(page.locator('textarea')).toContainText(badPrompt)
|
||||
|
||||
// Select all and start a new prompt.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.press('KeyA')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.type('a 2x4 lego')
|
||||
|
||||
// Submit the new prompt.
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Make sure the new prompt works.
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(submittingToastMessage).toBeVisible()
|
||||
|
||||
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||
|
||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||
})
|
||||
|
||||
test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandBarButton).toBeVisible()
|
||||
// Click the command bar button
|
||||
await commandBarButton.click()
|
||||
|
||||
// Wait for the command bar to appear
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
const textToCadCommand = page.getByText('Text-to-CAD')
|
||||
await expect(textToCadCommand.first()).toBeVisible()
|
||||
// Click the Text-to-CAD command
|
||||
await textToCadCommand.first().click()
|
||||
|
||||
// Enter the prompt.
|
||||
const prompt = page.getByText('Prompt')
|
||||
await expect(prompt.first()).toBeVisible()
|
||||
|
||||
const badPrompt =
|
||||
'akjsndladf lajbhflauweyfa;wieufjn;wieJNUF;.wjdfn weh Fwhefb'
|
||||
|
||||
// Type the prompt.
|
||||
await page.keyboard.type(badPrompt)
|
||||
await page.waitForTimeout(1000)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const submittingToastMessage = page.getByText(
|
||||
`Submitting to Text-to-CAD API...`
|
||||
)
|
||||
await expect(submittingToastMessage).toBeVisible()
|
||||
|
||||
const generatingToastMessage = page.getByText(
|
||||
`Generating parametric model...`
|
||||
)
|
||||
await expect(generatingToastMessage).toBeVisible()
|
||||
|
||||
const failureToastMessage = page.getByText(
|
||||
`The prompt must clearly describe a CAD model`
|
||||
)
|
||||
await expect(failureToastMessage).toBeVisible()
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
// Make sure the toast did not say it was successful.
|
||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||
await expect(successToastMessage).not.toBeVisible()
|
||||
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||
|
||||
// They should be able to try again from the command bar.
|
||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(submittingToastMessage).toBeVisible()
|
||||
|
||||
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||
|
||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||
|
||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||
|
||||
// old failure toast should stick around.
|
||||
await expect(failureToastMessage).toBeVisible()
|
||||
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||
})
|
||||
|
||||
test('ensure you can shift+enter in the prompt box', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const promptWithNewline = `a 2x4\nlego`
|
||||
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandBarButton).toBeVisible()
|
||||
// Click the command bar button
|
||||
await commandBarButton.click()
|
||||
|
||||
// Wait for the command bar to appear
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
const textToCadCommand = page.getByText('Text-to-CAD')
|
||||
await expect(textToCadCommand.first()).toBeVisible()
|
||||
// Click the Text-to-CAD command
|
||||
await textToCadCommand.first().click()
|
||||
|
||||
// Enter the prompt.
|
||||
const prompt = page.getByText('Prompt')
|
||||
await expect(prompt.first()).toBeVisible()
|
||||
|
||||
// Type the prompt.
|
||||
await page.keyboard.type('a 2x4')
|
||||
await page.waitForTimeout(1000)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.up('Shift')
|
||||
await page.keyboard.type('lego')
|
||||
await page.waitForTimeout(1000)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const submittingToastMessage = page.getByText(
|
||||
`Submitting to Text-to-CAD API...`
|
||||
)
|
||||
await expect(submittingToastMessage).toBeVisible()
|
||||
|
||||
const generatingToastMessage = page.getByText(
|
||||
`Generating parametric model...`
|
||||
)
|
||||
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||
|
||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||
|
||||
await expect(page.getByText(promptWithNewline)).toBeVisible()
|
||||
})
|
||||
|
||||
test('can do many at once and get many prompts back, and interact with many', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||
|
||||
await sendPromptFromCommandBar(page, 'a 2x8 lego')
|
||||
|
||||
await sendPromptFromCommandBar(page, 'a 2x10 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const submittingToastMessage = page.getByText(
|
||||
`Submitting to Text-to-CAD API...`
|
||||
)
|
||||
await expect(submittingToastMessage.first()).toBeVisible()
|
||||
|
||||
const generatingToastMessage = page.getByText(
|
||||
`Generating parametric model...`
|
||||
)
|
||||
await expect(generatingToastMessage.first()).toBeVisible({ timeout: 10000 })
|
||||
|
||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||
// We should have three success toasts.
|
||||
await expect(successToastMessage).toHaveCount(3, { timeout: 15000 })
|
||||
|
||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||
|
||||
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||
await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
|
||||
await expect(page.getByText(`a 2x10 lego`)).toBeVisible()
|
||||
|
||||
// Ensure if you reject one, the others stay.
|
||||
const rejectButton = page.getByRole('button', { name: 'Reject' })
|
||||
await expect(rejectButton.first()).toBeVisible()
|
||||
// Click the reject button on the first toast.
|
||||
await rejectButton.first().click()
|
||||
|
||||
// The first toast should disappear, but not the others.
|
||||
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
||||
await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
|
||||
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||
|
||||
// Ensure you can copy the code for one of the models remaining.
|
||||
const copyToClipboardButton = page.getByRole('button', {
|
||||
name: 'Copy to clipboard',
|
||||
})
|
||||
await expect(copyToClipboardButton.first()).toBeVisible()
|
||||
// Click the button.
|
||||
await copyToClipboardButton.first().click()
|
||||
|
||||
// Expect the code to be copied.
|
||||
await expect(page.getByText('Copied')).toBeVisible()
|
||||
|
||||
// Click in the code editor.
|
||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||
|
||||
// Paste the code.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.press('KeyV')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
|
||||
// Expect the code to be pasted.
|
||||
await expect(page.locator('.cm-content')).toContainText(`2x8`)
|
||||
|
||||
// Find the toast close button.
|
||||
const closeButton = page.getByRole('button', { name: 'Close' })
|
||||
await expect(closeButton).toBeVisible()
|
||||
await closeButton.click()
|
||||
|
||||
// Ensure the final toast remains.
|
||||
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
||||
await expect(page.getByText(`a 2x8 lego`)).not.toBeVisible()
|
||||
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||
|
||||
// Ensure you can copy the code for the final model.
|
||||
await expect(copyToClipboardButton).toBeVisible()
|
||||
// Click the button.
|
||||
await copyToClipboardButton.click()
|
||||
|
||||
// Expect the code to be copied.
|
||||
await expect(page.getByText('Copied')).toBeVisible()
|
||||
|
||||
// Click in the code editor.
|
||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||
|
||||
// Paste the code.
|
||||
await page.keyboard.press('ControlOrMeta+a')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('ControlOrMeta+v')
|
||||
|
||||
// Expect the code to be pasted.
|
||||
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
||||
|
||||
// Expect the toast to disappear.
|
||||
// Find the toast close button.
|
||||
await expect(closeButton).toBeVisible()
|
||||
await closeButton.click()
|
||||
await expect(successToastMessage).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||
|
||||
await sendPromptFromCommandBar(
|
||||
page,
|
||||
'alkjsdnlajshdbfjlhsbdf a;skjdnf;askjdnf'
|
||||
)
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const submittingToastMessage = page.getByText(
|
||||
`Submitting to Text-to-CAD API...`
|
||||
)
|
||||
await expect(submittingToastMessage.first()).toBeVisible()
|
||||
|
||||
const generatingToastMessage = page.getByText(
|
||||
`Generating parametric model...`
|
||||
)
|
||||
await expect(generatingToastMessage.first()).toBeVisible({ timeout: 10000 })
|
||||
|
||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||
// We should have three success toasts.
|
||||
await expect(successToastMessage).toHaveCount(1, { timeout: 15000 })
|
||||
|
||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||
|
||||
const failureToastMessage = page.getByText(
|
||||
`The prompt must clearly describe a CAD model`
|
||||
)
|
||||
await expect(failureToastMessage).toBeVisible()
|
||||
|
||||
// Make sure the toast did not say it was successful.
|
||||
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||
|
||||
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||
|
||||
// Ensure if you dismiss the error the others stay.
|
||||
const dismissButton = page.getByRole('button', { name: 'Dismiss' })
|
||||
await expect(dismissButton).toBeVisible()
|
||||
// Click the dismiss button on the first toast.
|
||||
await dismissButton.first().click()
|
||||
|
||||
// Make sure the failure toast disappears.
|
||||
await expect(failureToastMessage).not.toBeVisible()
|
||||
await expect(page.getByText(`Text-to-CAD failed`)).not.toBeVisible()
|
||||
|
||||
// The first toast should disappear, but not the others.
|
||||
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||
|
||||
// Ensure you can copy the code for one of the models remaining.
|
||||
const copyToClipboardButton = page.getByRole('button', {
|
||||
name: 'Copy to clipboard',
|
||||
})
|
||||
await expect(copyToClipboardButton.first()).toBeVisible()
|
||||
// Click the button.
|
||||
await copyToClipboardButton.first().click()
|
||||
|
||||
// Expect the code to be copied.
|
||||
await expect(page.getByText('Copied')).toBeVisible()
|
||||
|
||||
// Click in the code editor.
|
||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||
|
||||
// Paste the code.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.press('KeyV')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
|
||||
// Expect the code to be pasted.
|
||||
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
||||
|
||||
// Find the toast close button.
|
||||
const closeButton = page.getByRole('button', { name: 'Close' })
|
||||
await expect(closeButton).toBeVisible()
|
||||
await closeButton.click()
|
||||
|
||||
// Expect the toast to disappear.
|
||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||
await expect(successToastMessage).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
async function sendPromptFromCommandBar(page: Page, promptStr: string) {
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandBarButton).toBeVisible()
|
||||
// Click the command bar button
|
||||
await commandBarButton.click()
|
||||
|
||||
// Wait for the command bar to appear
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API ')
|
||||
await expect(textToCadCommand.first()).toBeVisible()
|
||||
// Click the Text-to-CAD command
|
||||
await textToCadCommand.first().click()
|
||||
|
||||
// Enter the prompt.
|
||||
const prompt = page.getByText('Prompt')
|
||||
await expect(prompt.first()).toBeVisible()
|
||||
|
||||
// Type the prompt.
|
||||
await page.keyboard.type(promptStr)
|
||||
await page.waitForTimeout(1000)
|
||||
await page.keyboard.press('Enter')
|
||||
}
|
@ -264,6 +264,8 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
||||
})
|
||||
|
||||
test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// This test can run long if it takes a little too long to load
|
||||
// the engine.
|
||||
test.setTimeout(90000)
|
||||
@ -273,131 +275,162 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
||||
'weird playwright bug on ubuntu https://github.com/KittyCAD/modeling-app/issues/2444'
|
||||
)
|
||||
// Load the app with the code pane open
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'store',
|
||||
JSON.stringify({
|
||||
state: {
|
||||
openPanes: ['code'],
|
||||
},
|
||||
version: 0,
|
||||
})
|
||||
)
|
||||
|
||||
await test.step(`Set up test`, async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'store',
|
||||
JSON.stringify({
|
||||
state: {
|
||||
openPanes: ['code'],
|
||||
},
|
||||
version: 0,
|
||||
})
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
})
|
||||
|
||||
// Wait for the app to be ready for use
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const codePane = page.getByRole('textbox').locator('div')
|
||||
const codePaneButton = page.getByTestId('code-pane-button')
|
||||
const codePane = page.locator('.cm-content')
|
||||
const lineButton = page.getByRole('button', { name: 'Line', exact: true })
|
||||
const arcButton = page.getByRole('button', {
|
||||
name: 'Tangential Arc',
|
||||
exact: true,
|
||||
})
|
||||
const extrudeButton = page.getByRole('button', { name: 'Extrude' })
|
||||
const commandBarComboBox = page.getByPlaceholder('Search commands')
|
||||
const exitSketchButton = page.getByRole('button', { name: 'Exit Sketch' })
|
||||
|
||||
// Test that the hotkeys do nothing when
|
||||
// focus is on the code pane
|
||||
await codePane.click()
|
||||
await page.keyboard.press('/')
|
||||
await page.keyboard.press('/')
|
||||
await page.keyboard.press('s')
|
||||
await page.keyboard.press('l')
|
||||
await page.keyboard.press('a')
|
||||
await page.keyboard.press('e')
|
||||
await expect(page.locator('.cm-content')).toHaveText('//slae')
|
||||
await page.keyboard.press('Meta+/')
|
||||
await page.waitForTimeout(1000)
|
||||
// Test these hotkeys perform actions when
|
||||
// focus is on the canvas
|
||||
await page.mouse.move(600, 250)
|
||||
await page.mouse.click(600, 250)
|
||||
|
||||
// work-around: to stop "keyboard.press('s')" from typing in the editor even when it should be blurred
|
||||
await page.getByRole('button', { name: 'Commands' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Escape')
|
||||
await page.waitForTimeout(100)
|
||||
// end work-around
|
||||
|
||||
// Start a sketch
|
||||
await page.keyboard.press('s')
|
||||
await page.waitForTimeout(1000)
|
||||
await page.mouse.move(800, 300, { steps: 5 })
|
||||
await page.mouse.click(800, 300)
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
|
||||
timeout: 15_000,
|
||||
await test.step(`Type code with modeling hotkeys, shouldn't fire`, async () => {
|
||||
await codePane.click()
|
||||
await page.keyboard.type('//')
|
||||
await page.keyboard.press('s')
|
||||
await expect(commandBarComboBox).not.toBeVisible()
|
||||
await page.keyboard.press('e')
|
||||
await expect(commandBarComboBox).not.toBeVisible()
|
||||
await expect(codePane).toHaveText('//se')
|
||||
})
|
||||
|
||||
// Blur focus from the code editor, use the s command to sketch
|
||||
await test.step(`Blur editor focus, enter sketch`, async () => {
|
||||
/**
|
||||
* TODO: There is a bug somewhere that causes this test to fail
|
||||
* if you toggle the codePane closed before your trigger the
|
||||
* start of the sketch.
|
||||
* and a separate Safari-only bug that causes the test to fail
|
||||
* if the pane is open the entire test. The maintainer of CodeMirror
|
||||
* has pinpointed this to the unusual browser behavior:
|
||||
* https://discuss.codemirror.net/t/how-to-force-unfocus-of-the-codemirror-element-in-safari/8095/3
|
||||
*/
|
||||
await blurCodeEditor()
|
||||
await page.waitForTimeout(1000)
|
||||
await page.keyboard.press('s')
|
||||
await page.waitForTimeout(1000)
|
||||
await page.mouse.move(800, 300, { steps: 5 })
|
||||
await page.mouse.click(800, 300)
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
|
||||
timeout: 15_000,
|
||||
})
|
||||
})
|
||||
|
||||
// Use some sketch hotkeys to create a sketch (l and a for now)
|
||||
await test.step(`Incomplete sketch with hotkeys`, async () => {
|
||||
await test.step(`Draw a line`, async () => {
|
||||
await page.mouse.move(700, 200, { steps: 5 })
|
||||
await page.mouse.click(700, 200)
|
||||
await page.mouse.move(800, 250, { steps: 5 })
|
||||
await page.mouse.click(800, 250)
|
||||
})
|
||||
|
||||
await test.step(`Unequip line tool`, async () => {
|
||||
await page.keyboard.press('l')
|
||||
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
|
||||
await test.step(`Draw a tangential arc`, async () => {
|
||||
await page.keyboard.press('a')
|
||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'true', {
|
||||
timeout: 10_000,
|
||||
})
|
||||
await page.mouse.move(1000, 100, { steps: 5 })
|
||||
await page.mouse.click(1000, 100)
|
||||
})
|
||||
|
||||
await test.step(`Unequip with escape, equip line tool`, async () => {
|
||||
await page.keyboard.press('Escape')
|
||||
await page.keyboard.press('l')
|
||||
await page.waitForTimeout(50)
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
})
|
||||
|
||||
await test.step(`Type code with sketch hotkeys, shouldn't fire`, async () => {
|
||||
// Since there's code now, we have to get to the end of the line
|
||||
await page.locator('.cm-line').last().click()
|
||||
await page.keyboard.press('ControlOrMeta+ArrowRight')
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('//')
|
||||
await page.keyboard.press('l')
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await page.keyboard.press('a')
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await expect(codePane).toContainText('//la')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Backspace')
|
||||
})
|
||||
|
||||
await test.step(`Close profile and exit sketch`, async () => {
|
||||
await blurCodeEditor()
|
||||
await page.mouse.move(700, 200, { steps: 5 })
|
||||
await page.mouse.click(700, 200)
|
||||
// On close it will unequip the line tool.
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
|
||||
await expect(exitSketchButton).toBeEnabled()
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
// Extrude with e
|
||||
await test.step(`Extrude the sketch`, async () => {
|
||||
await page.mouse.click(750, 150)
|
||||
await blurCodeEditor()
|
||||
await expect(extrudeButton).toBeEnabled()
|
||||
await page.keyboard.press('e')
|
||||
await page.waitForTimeout(500)
|
||||
await page.mouse.move(800, 200, { steps: 5 })
|
||||
await page.mouse.click(800, 200)
|
||||
await page.waitForTimeout(500)
|
||||
await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Continue' }).click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Submit command' })
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Submit command' }).click()
|
||||
await expect(page.locator('.cm-content')).toContainText('extrude(')
|
||||
})
|
||||
|
||||
// await codePaneButton.click()
|
||||
// await expect(u.codeLocator).not.toBeVisible()
|
||||
|
||||
/**
|
||||
* TODO: There is a bug somewhere that causes this test to fail
|
||||
* if you toggle the codePane closed before your trigger the
|
||||
* start of the sketch.
|
||||
* and a separate Safari-only bug that causes the test to fail
|
||||
* if the pane is open the entire test. The maintainer of CodeMirror
|
||||
* has pinpointed this to the unusual browser behavior:
|
||||
* https://discuss.codemirror.net/t/how-to-force-unfocus-of-the-codemirror-element-in-safari/8095/3
|
||||
* work-around: to stop `keyboard.press()` from typing in the editor even when it should be blurred
|
||||
*/
|
||||
await codePaneButton.click()
|
||||
await expect(u.codeLocator).not.toBeVisible()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Draw a line
|
||||
await page.mouse.move(700, 200, { steps: 5 })
|
||||
await page.mouse.click(700, 200)
|
||||
await page.waitForTimeout(300)
|
||||
await page.mouse.move(800, 250, { steps: 5 })
|
||||
await page.mouse.click(800, 250)
|
||||
// Unequip line tool
|
||||
await page.keyboard.press('l')
|
||||
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
|
||||
// Equip arc tool
|
||||
await page.keyboard.press('a')
|
||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'true', {
|
||||
timeout: 10_000,
|
||||
})
|
||||
await page.mouse.move(1000, 100, { steps: 5 })
|
||||
await page.mouse.click(1000, 100)
|
||||
await page.keyboard.press('Escape')
|
||||
await page.keyboard.press('l')
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
// Close profile
|
||||
await page.mouse.move(700, 200, { steps: 5 })
|
||||
await page.mouse.click(700, 200)
|
||||
// On close it will unequip the line tool.
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
|
||||
// Exit sketch
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).not.toBeVisible()
|
||||
await page.waitForTimeout(400)
|
||||
|
||||
// Extrude
|
||||
await page.mouse.click(750, 150)
|
||||
await expect(extrudeButton).not.toBeDisabled()
|
||||
await page.keyboard.press('e')
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.move(800, 200, { steps: 5 })
|
||||
await page.mouse.click(800, 200)
|
||||
await page.waitForTimeout(300)
|
||||
await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Continue' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Submit command' })
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Submit command' }).click()
|
||||
|
||||
await codePaneButton.click()
|
||||
await expect(page.locator('.cm-content')).toContainText('extrude(')
|
||||
async function blurCodeEditor() {
|
||||
await page.getByRole('button', { name: 'Commands' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Escape')
|
||||
await page.waitForTimeout(100)
|
||||
}
|
||||
})
|
||||
|
||||
test('Delete key does not navigate back', async ({ page }) => {
|
||||
|
@ -188,6 +188,67 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"LiveView": {
|
||||
"description": "A liveview message.",
|
||||
"oneOf": [
|
||||
{
|
||||
"additionalProperties": true,
|
||||
"description": "Initialize the live view.",
|
||||
"properties": {
|
||||
"command": {
|
||||
"enum": [
|
||||
"init"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"op_protocols": {
|
||||
"description": "The op protocols.",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/OperationProtocol"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"peer_host": {
|
||||
"description": "The peer host.",
|
||||
"type": "string"
|
||||
},
|
||||
"reason": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Reason"
|
||||
}
|
||||
],
|
||||
"description": "The reason for the message.",
|
||||
"nullable": true
|
||||
},
|
||||
"result": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Result"
|
||||
}
|
||||
],
|
||||
"description": "The result of the command."
|
||||
},
|
||||
"sequence_id": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/SequenceId"
|
||||
}
|
||||
],
|
||||
"description": "The sequence id."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command",
|
||||
"op_protocols",
|
||||
"peer_host",
|
||||
"result",
|
||||
"sequence_id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Machine": {
|
||||
"description": "Details for a 3d printer connected over USB.",
|
||||
"oneOf": [
|
||||
@ -383,6 +444,32 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "A security message.",
|
||||
"properties": {
|
||||
"security": {
|
||||
"$ref": "#/components/schemas/Security"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"security"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "A liveview message.",
|
||||
"properties": {
|
||||
"live_view": {
|
||||
"$ref": "#/components/schemas/LiveView"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"live_view"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "An unknown Json message.",
|
||||
@ -448,6 +535,25 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"OperationProtocol": {
|
||||
"additionalProperties": true,
|
||||
"description": "An operation protocol.",
|
||||
"properties": {
|
||||
"protocol": {
|
||||
"description": "The protocol.",
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"description": "The version.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"protocol",
|
||||
"version"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Pong": {
|
||||
"description": "The response from the `/ping` endpoint.",
|
||||
"properties": {
|
||||
@ -512,6 +618,116 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": true,
|
||||
"description": "Ams change filament.",
|
||||
"properties": {
|
||||
"command": {
|
||||
"enum": [
|
||||
"ams_change_filament"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"errorno": {
|
||||
"description": "The error number.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"reason": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Reason"
|
||||
}
|
||||
],
|
||||
"description": "The reason for the message.",
|
||||
"nullable": true
|
||||
},
|
||||
"result": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Result"
|
||||
}
|
||||
],
|
||||
"description": "The result of the command."
|
||||
},
|
||||
"sequence_id": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/SequenceId"
|
||||
}
|
||||
],
|
||||
"description": "The sequence id."
|
||||
},
|
||||
"tar_temp": {
|
||||
"description": "The target temperature.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"target": {
|
||||
"description": "The target.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command",
|
||||
"errorno",
|
||||
"result",
|
||||
"sequence_id",
|
||||
"tar_temp",
|
||||
"target"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": true,
|
||||
"description": "Calibration.",
|
||||
"properties": {
|
||||
"command": {
|
||||
"enum": [
|
||||
"calibration"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"option": {
|
||||
"description": "The option.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"reason": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Reason"
|
||||
}
|
||||
],
|
||||
"description": "The reason for the message.",
|
||||
"nullable": true
|
||||
},
|
||||
"result": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Result"
|
||||
}
|
||||
],
|
||||
"description": "The result of the command."
|
||||
},
|
||||
"sequence_id": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/SequenceId"
|
||||
}
|
||||
],
|
||||
"description": "The sequence id."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command",
|
||||
"option",
|
||||
"result",
|
||||
"sequence_id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": true,
|
||||
"description": "The status of the print.",
|
||||
@ -1037,6 +1253,54 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": true,
|
||||
"description": "Print speed.",
|
||||
"properties": {
|
||||
"command": {
|
||||
"enum": [
|
||||
"print_speed"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"param": {
|
||||
"description": "The param.",
|
||||
"type": "string"
|
||||
},
|
||||
"reason": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Reason"
|
||||
}
|
||||
],
|
||||
"description": "The reason for the message.",
|
||||
"nullable": true
|
||||
},
|
||||
"result": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Result"
|
||||
}
|
||||
],
|
||||
"description": "The result of the command."
|
||||
},
|
||||
"sequence_id": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/SequenceId"
|
||||
}
|
||||
],
|
||||
"description": "The sequence id."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command",
|
||||
"param",
|
||||
"result",
|
||||
"sequence_id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": true,
|
||||
"description": "Resume the print.",
|
||||
@ -1610,6 +1874,83 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Security": {
|
||||
"description": "A security message.",
|
||||
"oneOf": [
|
||||
{
|
||||
"additionalProperties": true,
|
||||
"description": "Get the serial number.",
|
||||
"properties": {
|
||||
"address": {
|
||||
"description": "The address.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"chip_sn": {
|
||||
"description": "The chip sn.",
|
||||
"type": "string"
|
||||
},
|
||||
"chipsn_len": {
|
||||
"description": "The chip sn length.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"command": {
|
||||
"enum": [
|
||||
"get_sn"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"length": {
|
||||
"description": "The length.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"module": {
|
||||
"description": "The module.",
|
||||
"type": "string"
|
||||
},
|
||||
"reason": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Reason"
|
||||
}
|
||||
],
|
||||
"description": "The reason for the message.",
|
||||
"nullable": true
|
||||
},
|
||||
"sequence_id": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/SequenceId"
|
||||
}
|
||||
],
|
||||
"description": "The sequence id."
|
||||
},
|
||||
"sn": {
|
||||
"description": "The serial number.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "The status.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"address",
|
||||
"chip_sn",
|
||||
"chipsn_len",
|
||||
"command",
|
||||
"length",
|
||||
"module",
|
||||
"sequence_id",
|
||||
"sn",
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"SequenceId": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.24.10",
|
||||
"version": "0.24.13",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.17.0",
|
||||
@ -17,7 +17,7 @@
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.19",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@kittycad/lib": "^0.0.70",
|
||||
"@kittycad/lib": "^0.0.76",
|
||||
"@lezer/highlight": "^1.2.0",
|
||||
"@lezer/lr": "^1.4.1",
|
||||
"@react-hook/resize-observer": "^2.0.1",
|
||||
@ -37,7 +37,7 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"decamelize": "^6.0.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.5.5",
|
||||
"html2canvas-pro": "^1.5.8",
|
||||
"json-rpc-2.0": "^1.6.0",
|
||||
"jszip": "^3.10.1",
|
||||
"re-resizable": "^6.9.11",
|
||||
|
17
src-tauri/Cargo.lock
generated
@ -1214,7 +1214,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "derive-docs"
|
||||
version = "0.1.22"
|
||||
version = "0.1.23"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"convert_case 0.6.0",
|
||||
@ -2612,7 +2612,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx",
|
||||
@ -2672,9 +2672,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad"
|
||||
version = "0.3.12"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bc87dcc307aa8c8dd56a6f022da1cbdf13a0a1e2abacb9ca9f897118a75596d"
|
||||
checksum = "ce5e9c51976882cdf6777557fd8c3ee68b00bb53e9307fc1721acb397f2ece9a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -2704,6 +2704,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
@ -4587,9 +4588,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.206"
|
||||
version = "1.0.207"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284"
|
||||
checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -4616,9 +4617,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.206"
|
||||
version = "1.0.207"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97"
|
||||
checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|