Skip to content

Commit

Permalink
Merge branch 'main' into franknoirot/4442/edit-operations
Browse files Browse the repository at this point in the history
  • Loading branch information
franknoirot committed Jan 17, 2025
2 parents 00b5890 + dac91d3 commit 2e5ad09
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 17 deletions.
2 changes: 1 addition & 1 deletion e2e/playwright/code-pane-and-errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ test(

await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('router-template-slate')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible()
await expect(page.getByText('Create project')).toBeVisible()
})

await test.step('Opening the router-template project should load', async () => {
Expand Down
16 changes: 16 additions & 0 deletions e2e/playwright/fixtures/cmdBarFixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,20 @@ export class CmdBarFixture {
await promptEditCommand.first().click()
}
}

get cmdSearchInput() {
return this.page.getByTestId('cmd-bar-search')
}

get argumentInput() {
return this.page.getByTestId('cmd-bar-arg-value')
}

get cmdOptions() {
return this.page.getByTestId('cmd-bar-option')
}

chooseCommand = async (commandName: string) => {
await this.cmdOptions.getByText(commandName).click()
}
}
4 changes: 4 additions & 0 deletions e2e/playwright/fixtures/toolbarFixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export class ToolbarFixture {
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
}

get logoLink() {
return this.page.getByTestId('app-logo')
}

startSketchPlaneSelection = async () =>
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)

Expand Down
111 changes: 106 additions & 5 deletions e2e/playwright/projects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ test(
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('broken-code')).toBeVisible()
await expect(page.getByText('bracket')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible()
await expect(page.getByText('Create project')).toBeVisible()
})
await test.step('opening broken code project should clear the scene and show the error', async () => {
// Go back home.
Expand Down Expand Up @@ -253,7 +253,7 @@ test(
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('empty')).toBeVisible()
await expect(page.getByText('bracket')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible()
await expect(page.getByText('Create project')).toBeVisible()
})
await test.step('opening empty code project should clear the scene', async () => {
// Go back home.
Expand Down Expand Up @@ -985,6 +985,107 @@ test.describe(`Project management commands`, () => {
})
}
)
test(`Create a new project with a colliding name`, async ({
context,
homePage,
toolbar,
cmdBar,
}) => {
const projectName = 'test-project'
await test.step(`Setup`, async () => {
await context.folderSetupFn(async (dir) => {
const projectDir = path.join(dir, projectName)
await Promise.all([fsp.mkdir(projectDir, { recursive: true })])
await Promise.all([
fsp.copyFile(
executorInputPath('router-template-slate.kcl'),
path.join(projectDir, 'main.kcl')
),
])
})
await homePage.expectState({
projectCards: [
{
title: projectName,
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
})

await test.step('Create a new project with the same name', async () => {
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create project')
await cmdBar.expectState({
stage: 'arguments',
commandName: 'Create project',
currentArgKey: 'name',
currentArgValue: '',
headerArguments: {
Name: '',
},
highlightedHeaderArg: 'name',
})
await cmdBar.argumentInput.fill(projectName)
await cmdBar.progressCmdBar()
})

await test.step(`Check the project was created with a non-colliding name`, async () => {
await toolbar.logoLink.click()
await homePage.expectState({
projectCards: [
{
title: projectName + '-1',
fileCount: 1,
},
{
title: projectName,
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
})

await test.step('Create another project with the same name', async () => {
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create project')
await cmdBar.expectState({
stage: 'arguments',
commandName: 'Create project',
currentArgKey: 'name',
currentArgValue: '',
headerArguments: {
Name: '',
},
highlightedHeaderArg: 'name',
})
await cmdBar.argumentInput.fill(projectName)
await cmdBar.progressCmdBar()
})

await test.step(`Check the second project was created with a non-colliding name`, async () => {
await toolbar.logoLink.click()
await homePage.expectState({
projectCards: [
{
title: projectName + '-2',
fileCount: 1,
},
{
title: projectName + '-1',
fileCount: 1,
},
{
title: projectName,
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
})
})
})

test(
Expand Down Expand Up @@ -1391,7 +1492,7 @@ extrude001 = extrude(200, sketch001)`)
await page.getByTestId('app-logo').click()

await expect(
page.getByRole('button', { name: 'New project' })
page.getByRole('button', { name: 'Create project' })
).toBeVisible()

for (let i = 1; i <= 10; i++) {
Expand Down Expand Up @@ -1465,7 +1566,7 @@ test(

await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('router-template-slate')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible()
await expect(page.getByText('Create project')).toBeVisible()
})

await test.step('Opening the router-template project should load the stream', async () => {
Expand Down Expand Up @@ -1494,7 +1595,7 @@ test(

await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('router-template-slate')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible()
await expect(page.getByText('Create project')).toBeVisible()
})
}
)
Expand Down
2 changes: 1 addition & 1 deletion e2e/playwright/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,7 @@ export async function createProject({
returnHome?: boolean
}) {
await test.step(`Create project and navigate to it`, async () => {
await page.getByRole('button', { name: 'New project' }).click()
await page.getByRole('button', { name: 'Create project' }).click()
await page.getByRole('textbox', { name: 'Name' }).fill(name)
await page.getByRole('button', { name: 'Continue' }).click()

Expand Down
1 change: 1 addition & 0 deletions src/components/CommandBar/CommandArgOptionInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ function CommandArgOptionInput({
</label>
<Combobox.Input
id="option-input"
data-testid="cmd-bar-arg-value"
ref={inputRef}
onChange={(event) =>
!event.target.disabled && setQuery(event.target.value)
Expand Down
2 changes: 2 additions & 0 deletions src/components/CommandComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function CommandComboBox({
className="w-5 h-5 bg-primary/10 dark:bg-primary text-primary dark:text-inherit"
/>
<Combobox.Input
data-testid="cmd-bar-search"
onChange={(event) => setQuery(event.target.value)}
className="w-full bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none"
onKeyDown={(event) => {
Expand Down Expand Up @@ -85,6 +86,7 @@ function CommandComboBox({
value={option}
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90 ui-disabled:!text-chalkboard-50"
disabled={optionIsDisabled(option)}
data-testid={`cmd-bar-option`}
>
{'icon' in option && option.icon && (
<CustomIcon name={option.icon} className="w-5 h-5" />
Expand Down
11 changes: 4 additions & 7 deletions src/components/ProjectsContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getNextProjectIndex,
interpolateProjectNameWithIndex,
doesProjectNameNeedInterpolated,
getUniqueProjectName,
} from 'lib/desktopFS'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import useStateMachineCommands from 'hooks/useStateMachineCommands'
Expand Down Expand Up @@ -195,15 +196,11 @@ const ProjectsContextDesktop = ({
: settings.projects.defaultProjectName.current
).trim()

if (doesProjectNameNeedInterpolated(name)) {
const nextIndex = getNextProjectIndex(name, input.projects)
name = interpolateProjectNameWithIndex(name, nextIndex)
}

await createNewProjectDirectory(name)
const uniqueName = getUniqueProjectName(name, input.projects)
await createNewProjectDirectory(uniqueName)

return {
message: `Successfully created "${name}"`,
message: `Successfully created "${uniqueName}"`,
name,
}
}),
Expand Down
58 changes: 58 additions & 0 deletions src/lib/desktopFS.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { getUniqueProjectName } from './desktopFS'
import { FileEntry } from './project'

/** Create a dummy project */
function project(name: string, children?: FileEntry[]): FileEntry {
return {
name,
children: children || [
{ name: 'main.kcl', children: null, path: 'main.kcl' },
],
path: `/projects/${name}`,
}
}

describe(`Getting unique project names`, () => {
it(`should return the same name if no conflicts`, () => {
const projectName = 'new-project'
const projects = [project('existing-project'), project('another-project')]
const result = getUniqueProjectName(projectName, projects)
expect(result).toBe(projectName)
})
it(`should return a unique name if there is a conflict`, () => {
const projectName = 'existing-project'
const projects = [project('existing-project'), project('another-project')]
const result = getUniqueProjectName(projectName, projects)
expect(result).toBe('existing-project-1')
})
it(`should increment an ending index until a unique one is found`, () => {
const projectName = 'existing-project-1'
const projects = [
project('existing-project'),
project('existing-project-1'),
project('existing-project-2'),
]
const result = getUniqueProjectName(projectName, projects)
expect(result).toBe('existing-project-3')
})
it(`should prefer the formatting of the index identifier if present`, () => {
const projectName = 'existing-project-$nn'
const projects = [
project('existing-project'),
project('existing-project-1'),
project('existing-project-2'),
]
const result = getUniqueProjectName(projectName, projects)
expect(result).toBe('existing-project-03')
})
it(`be able to get an incrementing index regardless of padding zeroes`, () => {
const projectName = 'existing-project-$nn'
const projects = [
project('existing-project'),
project('existing-project-01'),
project('existing-project-2'),
]
const result = getUniqueProjectName(projectName, projects)
expect(result).toBe('existing-project-03')
})
})
33 changes: 31 additions & 2 deletions src/lib/desktopFS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ export function getNextProjectIndex(
const matches = projects.map((project) => project.name?.match(regex))
const indices = matches
.filter(Boolean)
.map((match) => match![1])
.map(Number)
.map((match) => (match !== null ? match[1] : '-1'))
.map((maybeMatchIndex) => {
return parseInt(maybeMatchIndex || '0', 10)
})
const maxIndex = Math.max(...indices, -1)
return maxIndex + 1
}
Expand Down Expand Up @@ -83,6 +85,33 @@ export function doesProjectNameNeedInterpolated(projectName: string) {
return projectName.includes(INDEX_IDENTIFIER)
}

/**
* Given a target name, which may include our magic index interpolation string,
* and a list of projects, return a unique name that doesn't conflict with any
* of the existing projects, incrementing any ending number if necessary.
* @param name
* @param projects
* @returns
*/
export function getUniqueProjectName(name: string, projects: FileEntry[]) {
// The name may have our magic index interpolation string in it
const needsInterpolation = doesProjectNameNeedInterpolated(name)

if (needsInterpolation) {
const nextIndex = getNextProjectIndex(name, projects)
return interpolateProjectNameWithIndex(name, nextIndex)
} else {
let newName = name
while (projects.some((project) => project.name === newName)) {
const nameEndsWithNumber = newName.match(/\d+$/)
newName = nameEndsWithNumber
? newName.replace(/\d+$/, (num) => `${parseInt(num, 10) + 1}`)
: `${name}-1`
}
return newName
}
}

function escapeRegExpChars(string: string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
Expand Down
2 changes: 1 addition & 1 deletion src/routes/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ const Home = () => {
}}
data-testid="home-new-file"
>
New project
Create project
</ActionButton>
</div>
<div className="flex gap-2 items-center">
Expand Down

0 comments on commit 2e5ad09

Please sign in to comment.