Franknoirot/bugfix directory picker (#2025)
* Fix project directory setting input * Remove unused imports * Almost working Tauri test * Finish Tauri e2e test * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * fmt * Try a different Webriver selector * Update themeColor component to use new updateValue API * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * Rerun CI --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Binary file not shown.
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Binary file not shown.
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 50 KiB |
Binary file not shown.
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
@ -1,7 +1,10 @@
|
|||||||
import { browser, $, expect } from '@wdio/globals'
|
import { browser, $, expect } from '@wdio/globals'
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
|
|
||||||
const defaultDir = `${process.env.HOME}/Documents/zoo-modeling-app-projects`
|
const documentsDir = `${process.env.HOME}/Documents`
|
||||||
|
const userSettingsFile = `${process.env.HOME}/.config/dev.zoo.modeling-app/user.toml`
|
||||||
|
const defaultProjectDir = `${documentsDir}/zoo-modeling-app-projects`
|
||||||
|
const newProjectDir = `${documentsDir}/a-different-directory`
|
||||||
const userCodeDir = '/tmp/kittycad_user_code'
|
const userCodeDir = '/tmp/kittycad_user_code'
|
||||||
|
|
||||||
async function click(element: WebdriverIO.Element): Promise<void> {
|
async function click(element: WebdriverIO.Element): Promise<void> {
|
||||||
@ -10,12 +13,25 @@ async function click(element: WebdriverIO.Element): Promise<void> {
|
|||||||
await browser.execute('arguments[0].click();', element)
|
await browser.execute('arguments[0].click();', element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Shoutout to @Sheap on Github for a great workaround utility:
|
||||||
|
* https://github.com/tauri-apps/tauri/issues/6541#issue-1638944060
|
||||||
|
*/
|
||||||
|
async function setDatasetValue(
|
||||||
|
field: WebdriverIO.Element,
|
||||||
|
property: string,
|
||||||
|
value: string
|
||||||
|
) {
|
||||||
|
await browser.execute(`arguments[0].dataset.${property} = "${value}"`, field)
|
||||||
|
}
|
||||||
|
|
||||||
describe('ZMA (Tauri, Linux)', () => {
|
describe('ZMA (Tauri, Linux)', () => {
|
||||||
it('opens the auth page and signs in', async () => {
|
it('opens the auth page and signs in', async () => {
|
||||||
// Clean up filesystem from previous tests
|
// Clean up filesystem from previous tests
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
await fs.rm(defaultDir, { force: true, recursive: true })
|
await fs.rm(defaultProjectDir, { force: true, recursive: true })
|
||||||
await fs.rm(userCodeDir, { force: true })
|
await fs.rm(userCodeDir, { force: true })
|
||||||
|
await fs.rm(userSettingsFile, { force: true })
|
||||||
|
await fs.mkdir(newProjectDir, { recursive: true })
|
||||||
|
|
||||||
const signInButton = await $('[data-testid="sign-in-button"]')
|
const signInButton = await $('[data-testid="sign-in-button"]')
|
||||||
expect(await signInButton.getText()).toEqual('Sign in')
|
expect(await signInButton.getText()).toEqual('Sign in')
|
||||||
@ -65,8 +81,20 @@ describe('ZMA (Tauri, Linux)', () => {
|
|||||||
const settingsButton = await $('[data-testid="settings-button"]')
|
const settingsButton = await $('[data-testid="settings-button"]')
|
||||||
await click(settingsButton)
|
await click(settingsButton)
|
||||||
|
|
||||||
const defaultDirInput = await $('[data-testid="default-directory-input"]')
|
const projectDirInput = await $('[data-testid="project-directory-input"]')
|
||||||
expect(await defaultDirInput.getValue()).toEqual(defaultDir)
|
expect(await projectDirInput.getValue()).toEqual(defaultProjectDir)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We've set up the project directory input (in initialSettings.tsx)
|
||||||
|
* to be able to skip the folder selection dialog if data-testValue
|
||||||
|
* has a value, allowing us to test the input otherwise works.
|
||||||
|
*/
|
||||||
|
await setDatasetValue(projectDirInput, 'testValue', newProjectDir)
|
||||||
|
const projectDirButton = await $('[data-testid="project-directory-button"]')
|
||||||
|
await click(projectDirButton)
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||||
|
// This line is broken. I need a different way to grab the toast
|
||||||
|
await expect(await $('div*=Set project directory to')).toBeDisplayed()
|
||||||
|
|
||||||
const nameInput = await $('[data-testid="projects-defaultProjectName"]')
|
const nameInput = await $('[data-testid="projects-defaultProjectName"]')
|
||||||
expect(await nameInput.getValue()).toEqual('project-$nnn')
|
expect(await nameInput.getValue()).toEqual('project-$nnn')
|
||||||
|
@ -135,6 +135,7 @@ export const SettingsAuthProviderBase = ({
|
|||||||
: '')
|
: '')
|
||||||
toast.success(message, {
|
toast.success(message, {
|
||||||
duration: message.split(' ').length * 100 + 1500,
|
duration: message.split(' ').length * 100 + 1500,
|
||||||
|
id: `${event.type}.success`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
'Execute AST': () => kclManager.executeAst(),
|
'Execute AST': () => kclManager.executeAst(),
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
cameraSystems,
|
cameraSystems,
|
||||||
} from 'lib/cameraControls'
|
} from 'lib/cameraControls'
|
||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
import { CSSProperties, useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
import { open } from '@tauri-apps/api/dialog'
|
import { open } from '@tauri-apps/api/dialog'
|
||||||
import { CustomIcon } from 'components/CustomIcon'
|
import { CustomIcon } from 'components/CustomIcon'
|
||||||
import Tooltip from 'components/Tooltip'
|
import Tooltip from 'components/Tooltip'
|
||||||
@ -136,11 +136,11 @@ export function createSettings() {
|
|||||||
defaultValue: '264.5',
|
defaultValue: '264.5',
|
||||||
description: 'The hue of the primary theme color for the app',
|
description: 'The hue of the primary theme color for the app',
|
||||||
validate: (v) => Number(v) >= 0 && Number(v) < 360,
|
validate: (v) => Number(v) >= 0 && Number(v) < 360,
|
||||||
Component: ({ value, onChange }) => (
|
Component: ({ value, updateValue }) => (
|
||||||
<div className="flex item-center gap-2 px-2">
|
<div className="flex item-center gap-2 px-2">
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
onChange={onChange}
|
onChange={(e) => updateValue(e.currentTarget.value)}
|
||||||
value={value}
|
value={value}
|
||||||
min={0}
|
min={0}
|
||||||
max={259}
|
max={259}
|
||||||
@ -176,35 +176,41 @@ export function createSettings() {
|
|||||||
hideOnLevel: 'project',
|
hideOnLevel: 'project',
|
||||||
hideOnPlatform: 'web',
|
hideOnPlatform: 'web',
|
||||||
validate: (v) => typeof v === 'string' && (v.length > 0 || !isTauri()),
|
validate: (v) => typeof v === 'string' && (v.length > 0 || !isTauri()),
|
||||||
Component: ({ value, onChange }) => {
|
Component: ({ value, updateValue }) => {
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-4 p-1 border rounded-sm border-chalkboard-30">
|
<div className="flex gap-4 p-1 border rounded-sm border-chalkboard-30">
|
||||||
<input
|
<input
|
||||||
className="flex-grow text-xs px-2 bg-transparent"
|
className="flex-grow text-xs px-2 bg-transparent"
|
||||||
value={value}
|
value={value}
|
||||||
onBlur={onChange}
|
|
||||||
disabled
|
disabled
|
||||||
data-testid="default-directory-input"
|
data-testid="project-directory-input"
|
||||||
|
ref={inputRef}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const newValue = await open({
|
// In Tauri end-to-end tests we can't control the file picker,
|
||||||
directory: true,
|
// so we seed the new directory value in the element's dataset
|
||||||
recursive: true,
|
const newValue =
|
||||||
defaultPath: value,
|
inputRef.current && inputRef.current.dataset.testValue
|
||||||
title: 'Choose a new default directory',
|
? inputRef.current.dataset.testValue
|
||||||
})
|
: await open({
|
||||||
|
directory: true,
|
||||||
|
recursive: true,
|
||||||
|
defaultPath: value,
|
||||||
|
title: 'Choose a new project directory',
|
||||||
|
})
|
||||||
if (
|
if (
|
||||||
inputRef.current &&
|
|
||||||
newValue &&
|
newValue &&
|
||||||
newValue !== null &&
|
newValue !== null &&
|
||||||
|
newValue !== value &&
|
||||||
!Array.isArray(newValue)
|
!Array.isArray(newValue)
|
||||||
) {
|
) {
|
||||||
inputRef.current.value = newValue
|
updateValue(newValue)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="p-0 m-0 border-none hover:bg-primary/10 focus:bg-primary/10 dark:hover:bg-primary/20 dark:focus::bg-primary/20"
|
className="p-0 m-0 border-none hover:bg-primary/10 focus:bg-primary/10 dark:hover:bg-primary/20 dark:focus::bg-primary/20"
|
||||||
|
data-testid="project-directory-button"
|
||||||
>
|
>
|
||||||
<CustomIcon name="folder" className="w-5 h-5" />
|
<CustomIcon name="folder" className="w-5 h-5" />
|
||||||
<Tooltip position="inlineStart">Choose a folder</Tooltip>
|
<Tooltip position="inlineStart">Choose a folder</Tooltip>
|
||||||
@ -264,13 +270,13 @@ export function createSettings() {
|
|||||||
],
|
],
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
Component: ({ value, onChange }) => (
|
Component: ({ value, updateValue }) => (
|
||||||
<>
|
<>
|
||||||
<select
|
<select
|
||||||
id="camera-controls"
|
id="camera-controls"
|
||||||
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
|
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={(e) => updateValue(e.target.value as CameraSystem)}
|
||||||
>
|
>
|
||||||
{cameraSystems.map((program) => (
|
{cameraSystems.map((program) => (
|
||||||
<option key={program} value={program}>
|
<option key={program} value={program}>
|
||||||
|
@ -96,7 +96,7 @@ export interface SettingProps<T = unknown> {
|
|||||||
*/
|
*/
|
||||||
Component?: React.ComponentType<{
|
Component?: React.ComponentType<{
|
||||||
value: T
|
value: T
|
||||||
onChange: ChangeEventHandler
|
updateValue: (newValue: T) => void
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,8 +172,8 @@ const Home = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [
|
}, [
|
||||||
settings.app.projectDirectory,
|
settings.app.projectDirectory.current,
|
||||||
settings.projects.defaultProjectName,
|
settings.projects.defaultProjectName.current,
|
||||||
send,
|
send,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -492,16 +492,14 @@ function GeneratedSetting({
|
|||||||
setting.Component && (
|
setting.Component && (
|
||||||
<setting.Component
|
<setting.Component
|
||||||
value={setting[settingsLevel] || setting.getFallback(settingsLevel)}
|
value={setting[settingsLevel] || setting.getFallback(settingsLevel)}
|
||||||
onChange={(e) => {
|
updateValue={(newValue) => {
|
||||||
if ('value' in e.target) {
|
send({
|
||||||
send({
|
type: `set.${category}.${settingName}`,
|
||||||
type: `set.${category}.${settingName}`,
|
data: {
|
||||||
data: {
|
level: settingsLevel,
|
||||||
level: settingsLevel,
|
value: newValue,
|
||||||
value: e.target.value,
|
},
|
||||||
},
|
} as unknown as Event<WildcardSetEvent>)
|
||||||
} as unknown as Event<WildcardSetEvent>)
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user