Files
modeling-app/e2e/playwright/testing-settings.spec.ts

550 lines
19 KiB
TypeScript
Raw Normal View History

import { test, expect } from '@playwright/test'
import * as fsp from 'fs/promises'
import { join } from 'path'
import {
getUtils,
setup,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { TEST_SETTINGS_KEY, TEST_SETTINGS_CORRUPTED } from './storageStates'
import * as TOML from '@iarna/toml'
test.beforeEach(async ({ context, page }) => {
await setup(context, page)
})
test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
test.describe('Testing settings', () => {
test('Stored settings are validated and fall back to defaults', async ({
page,
}) => {
const u = await getUtils(page)
// Override beforeEach test setup
// with corrupted settings
await page.addInitScript(
async ({ settingsKey, settings }) => {
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS_CORRUPTED }),
}
)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
// Check the settings were reset
const storedSettings = TOML.parse(
await page.evaluate(
({ settingsKey }) => localStorage.getItem(settingsKey) || '',
{ settingsKey: TEST_SETTINGS_KEY }
)
) as { settings: SaveSettingsPayload }
expect(storedSettings.settings?.app?.theme).toBe(undefined)
// Check that the invalid settings were removed
expect(storedSettings.settings?.modeling?.defaultUnit).toBe(undefined)
expect(storedSettings.settings?.modeling?.mouseControls).toBe(undefined)
expect(storedSettings.settings?.app?.projectDirectory).toBe(undefined)
expect(storedSettings.settings?.projects?.defaultProjectName).toBe(
undefined
)
})
test('Project settings can be set and override user settings', async ({
page,
}) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await page
.getByRole('button', { name: 'Start Sketch' })
.waitFor({ state: 'visible' })
const paneButtonLocator = page.getByTestId('debug-pane-button')
const headingLocator = page.getByRole('heading', {
name: 'Settings',
exact: true,
})
const inputLocator = page.locator('input[name="modeling-showDebugPanel"]')
// Open the settings modal with the browser keyboard shortcut
await page.keyboard.press('ControlOrMeta+Shift+,')
await expect(headingLocator).toBeVisible()
await page.locator('#showDebugPanel').getByText('OffOn').click()
// Close it and open again with keyboard shortcut, while KCL editor is focused
// Put the cursor in the editor
await test.step('Open settings with keyboard shortcut', async () => {
await page.getByTestId('settings-close-button').click()
await page.locator('.cm-content').click()
await page.keyboard.press('ControlOrMeta+Shift+,')
await expect(headingLocator).toBeVisible()
})
// Verify the toast appeared
await expect(
page.getByText(`Set show debug panel to "false" for this project`)
).toBeVisible()
// Check that the theme changed
await expect(paneButtonLocator).not.toBeVisible()
// Check that the user setting was not changed
await page.getByRole('radio', { name: 'User' }).click()
await expect(inputLocator).toBeChecked()
// Roll back to default of "off"
await await page
.getByText('show debug panelRoll back show debug panelRoll back to match')
.hover()
await page
.getByRole('button', {
name: 'Roll back show debug panel',
})
.click()
await expect(inputLocator).not.toBeChecked()
// Check that the project setting did not change
await page.getByRole('radio', { name: 'Project' }).click()
await expect(
page.locator('input[name="modeling-showDebugPanel"]')
).not.toBeChecked()
})
test('Keybindings display the correct hotkey for Command Palette', async ({
page,
}) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await test.step('Open keybindings settings', async () => {
// Open the settings modal with the browser keyboard shortcut
await page.keyboard.press('ControlOrMeta+Shift+,')
// Go to Keybindings tab.
const keybindingsTab = page.getByRole('radio', { name: 'Keybindings' })
await keybindingsTab.click()
})
// Go to the hotkey for Command Palette.
const commandPalette = page.getByText('Toggle Command Palette')
await commandPalette.scrollIntoViewIfNeeded()
// The heading is above it and should be in view now.
const commandPaletteHeading = page.getByRole('heading', {
name: 'Command Palette',
})
// The hotkey is in a kbd element next to the heading.
const hotkey = commandPaletteHeading.locator('+ div kbd')
const text = process.platform === 'darwin' ? 'Command+K' : 'Control+K'
await expect(hotkey).toHaveText(text)
})
test('Project and user settings can be reset', async ({ page }) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await page
.getByRole('button', { name: 'Start Sketch' })
.waitFor({ state: 'visible' })
const projectSettingsTab = page.getByRole('radio', { name: 'Project' })
const userSettingsTab = page.getByRole('radio', { name: 'User' })
const resetButton = page.getByRole('button', {
name: 'Restore default settings',
})
const themeColorSetting = page.locator('#themeColor').getByRole('slider')
const settingValues = {
default: '259',
user: '120',
project: '50',
}
// Open the settings modal with lower-right button
await page.getByRole('link', { name: 'Settings' }).last().click()
await expect(
page.getByRole('heading', { name: 'Settings', exact: true })
).toBeVisible()
await test.step('Set up theme color', async () => {
// Verify we're looking at the project-level settings,
// and it's set to default value
await expect(projectSettingsTab).toBeChecked()
await expect(themeColorSetting).toHaveValue(settingValues.default)
// Set project-level value to 50
await themeColorSetting.fill(settingValues.project)
// Set user-level value to 120
await userSettingsTab.click()
await themeColorSetting.fill(settingValues.user)
await projectSettingsTab.click()
})
await test.step('Reset project settings', async () => {
// Click the reset settings button.
await resetButton.click()
await expect(page.getByText('Settings restored to default')).toBeVisible()
await expect(
page.getByText('Settings restored to default')
).not.toBeVisible()
// Verify it is now set to the inherited user value
await expect(themeColorSetting).toHaveValue(settingValues.default)
// Check that the user setting also rolled back
await userSettingsTab.click()
await expect(themeColorSetting).toHaveValue(settingValues.default)
await projectSettingsTab.click()
// Set project-level value to 50 again to test the user-level reset
await themeColorSetting.fill(settingValues.project)
await userSettingsTab.click()
})
await test.step('Reset user settings', async () => {
// Change the setting and click the reset settings button.
await themeColorSetting.fill(settingValues.user)
await resetButton.click()
// Verify it is now set to the default value
await expect(themeColorSetting).toHaveValue(settingValues.default)
// Check that the project setting also changed
await projectSettingsTab.click()
await expect(themeColorSetting).toHaveValue(settingValues.default)
})
})
test(
`Project settings override user settings on desktop`,
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
2024-08-20 16:37:16 +10:00
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Selectors and constants
const userThemeColor = '120'
const projectThemeColor = '50'
const settingsOpenButton = page.getByRole('link', {
name: 'settings Settings',
})
const themeColorSetting = page.locator('#themeColor').getByRole('slider')
const projectSettingsTab = page.getByRole('radio', { name: 'Project' })
const userSettingsTab = page.getByRole('radio', { name: 'User' })
const settingsCloseButton = page.getByTestId('settings-close-button')
const projectLink = page.getByText('bracket')
const logoLink = page.getByTestId('app-logo')
// Open the app and set the user theme color
await test.step('Set user theme color on home', async () => {
await expect(settingsOpenButton).toBeVisible()
await settingsOpenButton.click()
// The user tab should be selected by default on home
await expect(userSettingsTab).toBeChecked()
await themeColorSetting.fill(userThemeColor)
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
await settingsCloseButton.click()
})
await test.step('Set project theme color', async () => {
// Open the project
await projectLink.click()
await settingsOpenButton.click()
// The project tab should be selected by default within a project
await expect(projectSettingsTab).toBeChecked()
await themeColorSetting.fill(projectThemeColor)
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
})
await test.step('Refresh the application and see project setting applied', async () => {
await page.reload({ waitUntil: 'domcontentloaded' })
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
await settingsCloseButton.click()
})
await test.step(`Navigate back to the home view and see user setting applied`, async () => {
await logoLink.click()
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
})
await electronApp.close()
}
)
2024-08-22 13:38:53 -04:00
test(
`Load desktop app with no settings file`,
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
const { electronApp, page } = await setupElectron({
// This is what makes no settings file get created
cleanProjectDir: false,
testInfo,
})
await page.setViewportSize({ width: 1200, height: 500 })
// Selectors and constants
const errorHeading = page.getByRole('heading', {
name: 'An unextected error occurred',
})
const projectDirLink = page.getByText('Loaded from')
// If the app loads without exploding we're in the clear
await expect(errorHeading).not.toBeVisible()
await expect(projectDirLink).toBeVisible()
await electronApp.close()
}
)
test(
`Load desktop app with a settings file, but no project directory setting`,
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
appSettings: {
app: {
themeColor: '259',
},
},
})
await page.setViewportSize({ width: 1200, height: 500 })
// Selectors and constants
const errorHeading = page.getByRole('heading', {
name: 'An unextected error occurred',
})
const projectDirLink = page.getByText('Loaded from')
// If the app loads without exploding we're in the clear
await expect(errorHeading).not.toBeVisible()
await expect(projectDirLink).toBeVisible()
await electronApp.close()
}
)
2024-08-22 13:38:53 -04:00
test(
`Closing settings modal should go back to the original file being viewed`,
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async () => {},
})
const {
openKclCodePanel,
openFilePanel,
2024-08-22 13:38:53 -04:00
createAndSelectProject,
pasteCodeInEditor,
createNewFileAndSelect,
editorTextMatches,
} = await getUtils(page, test)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await test.step('Precondition: No projects exist', async () => {
await expect(page.getByTestId('home-section')).toBeVisible()
const projectLinksPre = page.getByTestId('project-link')
await expect(projectLinksPre).toHaveCount(0)
})
await createAndSelectProject('project-000')
await openKclCodePanel()
2024-08-22 13:38:53 -04:00
const kclCube = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cube.kcl',
'utf-8'
)
await pasteCodeInEditor(kclCube)
await openFilePanel()
2024-08-22 13:38:53 -04:00
await createNewFileAndSelect('2.kcl')
const kclCylinder = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
'utf-8'
)
await pasteCodeInEditor(kclCylinder)
const settingsOpenButton = page.getByRole('link', {
name: 'settings Settings',
})
const settingsCloseButton = page.getByTestId('settings-close-button')
await test.step('Open and close settings', async () => {
await settingsOpenButton.click()
await settingsCloseButton.click()
})
await test.step('Postcondition: Same file content is in editor as before settings opened', async () => {
await editorTextMatches(kclCylinder)
})
await electronApp.close()
}
)
test('Changing modeling default unit', async ({ page }) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await page
.getByRole('button', { name: 'Start Sketch' })
.waitFor({ state: 'visible' })
const userSettingsTab = page.getByRole('radio', { name: 'User' })
// Open the settings modal with lower-right button
await page.getByRole('link', { name: 'Settings' }).last().click()
await expect(
page.getByRole('heading', { name: 'Settings', exact: true })
).toBeVisible()
const resetButton = page.getByRole('button', {
name: 'Restore default settings',
})
// Default unit should be mm
await resetButton.click()
await test.step('Change modeling default unit within project tab', async () => {
const changeUnitOfMeasureInProjectTab = async (unitOfMeasure: string) => {
await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => {
await page
.getByTestId('modeling-defaultUnit')
.selectOption(`${unitOfMeasure}`)
const toastMessage = page.getByText(
`Set default unit to "${unitOfMeasure}" for this project`
)
await expect(toastMessage).toBeVisible()
})
}
await changeUnitOfMeasureInProjectTab('in')
await changeUnitOfMeasureInProjectTab('ft')
await changeUnitOfMeasureInProjectTab('yd')
await changeUnitOfMeasureInProjectTab('mm')
await changeUnitOfMeasureInProjectTab('cm')
await changeUnitOfMeasureInProjectTab('m')
})
// Go to the user tab
await userSettingsTab.click()
await test.step('Change modeling default unit within user tab', async () => {
const changeUnitOfMeasureInUserTab = async (unitOfMeasure: string) => {
await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => {
await page
.getByTestId('modeling-defaultUnit')
.selectOption(`${unitOfMeasure}`)
const toastMessage = page.getByText(
`Set default unit to "${unitOfMeasure}" as a user default`
)
await expect(toastMessage).toBeVisible()
})
}
await changeUnitOfMeasureInUserTab('in')
await changeUnitOfMeasureInUserTab('ft')
await changeUnitOfMeasureInUserTab('yd')
await changeUnitOfMeasureInUserTab('mm')
await changeUnitOfMeasureInUserTab('cm')
await changeUnitOfMeasureInUserTab('m')
})
// Close settings
const settingsCloseButton = page.getByTestId('settings-close-button')
await settingsCloseButton.click()
await test.step('Change modeling default unit within command bar', async () => {
const commands = page.getByRole('button', { name: 'Commands' })
const changeUnitOfMeasureInCommandBar = async (unitOfMeasure: string) => {
// Open command bar
await commands.click()
const settingsModelingDefaultUnitCommand = page.getByText(
'Settings · modeling · default unit'
)
await settingsModelingDefaultUnitCommand.click()
const commandOption = page.getByRole('option', {
name: unitOfMeasure,
exact: true,
})
await commandOption.click()
const toastMessage = page.getByText(
`Set default unit to "${unitOfMeasure}" for this project`
)
await expect(toastMessage).toBeVisible()
}
await changeUnitOfMeasureInCommandBar('in')
await changeUnitOfMeasureInCommandBar('ft')
await changeUnitOfMeasureInCommandBar('yd')
await changeUnitOfMeasureInCommandBar('mm')
await changeUnitOfMeasureInCommandBar('cm')
await changeUnitOfMeasureInCommandBar('m')
})
await test.step('Change modeling default unit within gizmo', async () => {
const changeUnitOfMeasureInGizmo = async (
unitOfMeasure: string,
copy: string
) => {
const gizmo = page.getByRole('button', {
name: 'Current units are: ',
})
await gizmo.click()
const button = page.getByRole('button', {
name: copy,
exact: true,
})
await button.click()
const toastMessage = page.getByText(
`Set default unit to "${unitOfMeasure}" for this project`
)
await expect(toastMessage).toBeVisible()
}
await changeUnitOfMeasureInGizmo('in', 'Inches')
await changeUnitOfMeasureInGizmo('ft', 'Feet')
await changeUnitOfMeasureInGizmo('yd', 'Yards')
await changeUnitOfMeasureInGizmo('mm', 'Millimeters')
await changeUnitOfMeasureInGizmo('cm', 'Centimeters')
await changeUnitOfMeasureInGizmo('m', 'Meters')
})
})
})