Compare commits
	
		
			1 Commits
		
	
	
		
			nightly-v2
			...
			revert-510
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4322d3633e | 
| @ -280,7 +280,7 @@ test( | |||||||
|  |  | ||||||
|       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() |       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() | ||||||
|       await expect(page.getByText('router-template-slate')).toBeVisible() |       await expect(page.getByText('router-template-slate')).toBeVisible() | ||||||
|       await expect(page.getByText('Create project')).toBeVisible() |       await expect(page.getByText('New Project')).toBeVisible() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await test.step('Opening the router-template project should load', async () => { |     await test.step('Opening the router-template project should load', async () => { | ||||||
|  | |||||||
| @ -135,20 +135,4 @@ export class CmdBarFixture { | |||||||
|       await promptEditCommand.first().click() |       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() |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -63,10 +63,6 @@ export class ToolbarFixture { | |||||||
|     this.exeIndicator = page.getByTestId('model-state-indicator-execution-done') |     this.exeIndicator = page.getByTestId('model-state-indicator-execution-done') | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   get logoLink() { |  | ||||||
|     return this.page.getByTestId('app-logo') |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   startSketchPlaneSelection = async () => |   startSketchPlaneSelection = async () => | ||||||
|     doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500) |     doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500) | ||||||
|  |  | ||||||
|  | |||||||
| @ -172,7 +172,7 @@ test( | |||||||
|       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() |       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() | ||||||
|       await expect(page.getByText('broken-code')).toBeVisible() |       await expect(page.getByText('broken-code')).toBeVisible() | ||||||
|       await expect(page.getByText('bracket')).toBeVisible() |       await expect(page.getByText('bracket')).toBeVisible() | ||||||
|       await expect(page.getByText('Create project')).toBeVisible() |       await expect(page.getByText('New Project')).toBeVisible() | ||||||
|     }) |     }) | ||||||
|     await test.step('opening broken code project should clear the scene and show the error', async () => { |     await test.step('opening broken code project should clear the scene and show the error', async () => { | ||||||
|       // Go back home. |       // Go back home. | ||||||
| @ -253,7 +253,7 @@ test( | |||||||
|       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() |       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() | ||||||
|       await expect(page.getByText('empty')).toBeVisible() |       await expect(page.getByText('empty')).toBeVisible() | ||||||
|       await expect(page.getByText('bracket')).toBeVisible() |       await expect(page.getByText('bracket')).toBeVisible() | ||||||
|       await expect(page.getByText('Create project')).toBeVisible() |       await expect(page.getByText('New Project')).toBeVisible() | ||||||
|     }) |     }) | ||||||
|     await test.step('opening empty code project should clear the scene', async () => { |     await test.step('opening empty code project should clear the scene', async () => { | ||||||
|       // Go back home. |       // Go back home. | ||||||
| @ -985,107 +985,6 @@ 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( | test( | ||||||
| @ -1492,7 +1391,7 @@ extrude001 = extrude(200, sketch001)`) | |||||||
|     await page.getByTestId('app-logo').click() |     await page.getByTestId('app-logo').click() | ||||||
|  |  | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByRole('button', { name: 'Create project' }) |       page.getByRole('button', { name: 'New project' }) | ||||||
|     ).toBeVisible() |     ).toBeVisible() | ||||||
|  |  | ||||||
|     for (let i = 1; i <= 10; i++) { |     for (let i = 1; i <= 10; i++) { | ||||||
| @ -1566,7 +1465,7 @@ test( | |||||||
|  |  | ||||||
|       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() |       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() | ||||||
|       await expect(page.getByText('router-template-slate')).toBeVisible() |       await expect(page.getByText('router-template-slate')).toBeVisible() | ||||||
|       await expect(page.getByText('Create project')).toBeVisible() |       await expect(page.getByText('New Project')).toBeVisible() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await test.step('Opening the router-template project should load the stream', async () => { |     await test.step('Opening the router-template project should load the stream', async () => { | ||||||
| @ -1595,7 +1494,7 @@ test( | |||||||
|  |  | ||||||
|       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() |       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() | ||||||
|       await expect(page.getByText('router-template-slate')).toBeVisible() |       await expect(page.getByText('router-template-slate')).toBeVisible() | ||||||
|       await expect(page.getByText('Create project')).toBeVisible() |       await expect(page.getByText('New Project')).toBeVisible() | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -1078,7 +1078,7 @@ export async function createProject({ | |||||||
|   returnHome?: boolean |   returnHome?: boolean | ||||||
| }) { | }) { | ||||||
|   await test.step(`Create project and navigate to it`, async () => { |   await test.step(`Create project and navigate to it`, async () => { | ||||||
|     await page.getByRole('button', { name: 'Create project' }).click() |     await page.getByRole('button', { name: 'New project' }).click() | ||||||
|     await page.getByRole('textbox', { name: 'Name' }).fill(name) |     await page.getByRole('textbox', { name: 'Name' }).fill(name) | ||||||
|     await page.getByRole('button', { name: 'Continue' }).click() |     await page.getByRole('button', { name: 'Continue' }).click() | ||||||
|  |  | ||||||
|  | |||||||
| @ -134,7 +134,6 @@ function CommandArgOptionInput({ | |||||||
|           </label> |           </label> | ||||||
|           <Combobox.Input |           <Combobox.Input | ||||||
|             id="option-input" |             id="option-input" | ||||||
|             data-testid="cmd-bar-arg-value" |  | ||||||
|             ref={inputRef} |             ref={inputRef} | ||||||
|             onChange={(event) => |             onChange={(event) => | ||||||
|               !event.target.disabled && setQuery(event.target.value) |               !event.target.disabled && setQuery(event.target.value) | ||||||
|  | |||||||
| @ -52,7 +52,6 @@ function CommandComboBox({ | |||||||
|           className="w-5 h-5 bg-primary/10 dark:bg-primary text-primary dark:text-inherit" |           className="w-5 h-5 bg-primary/10 dark:bg-primary text-primary dark:text-inherit" | ||||||
|         /> |         /> | ||||||
|         <Combobox.Input |         <Combobox.Input | ||||||
|           data-testid="cmd-bar-search" |  | ||||||
|           onChange={(event) => setQuery(event.target.value)} |           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" |           className="w-full bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none" | ||||||
|           onKeyDown={(event) => { |           onKeyDown={(event) => { | ||||||
| @ -86,7 +85,6 @@ function CommandComboBox({ | |||||||
|             value={option} |             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" |             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)} |             disabled={optionIsDisabled(option)} | ||||||
|             data-testid={`cmd-bar-option`} |  | ||||||
|           > |           > | ||||||
|             {'icon' in option && option.icon && ( |             {'icon' in option && option.icon && ( | ||||||
|               <CustomIcon name={option.icon} className="w-5 h-5" /> |               <CustomIcon name={option.icon} className="w-5 h-5" /> | ||||||
|  | |||||||
| @ -18,7 +18,6 @@ import { | |||||||
|   getNextProjectIndex, |   getNextProjectIndex, | ||||||
|   interpolateProjectNameWithIndex, |   interpolateProjectNameWithIndex, | ||||||
|   doesProjectNameNeedInterpolated, |   doesProjectNameNeedInterpolated, | ||||||
|   getUniqueProjectName, |  | ||||||
| } from 'lib/desktopFS' | } from 'lib/desktopFS' | ||||||
| import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' | import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' | ||||||
| import useStateMachineCommands from 'hooks/useStateMachineCommands' | import useStateMachineCommands from 'hooks/useStateMachineCommands' | ||||||
| @ -196,11 +195,15 @@ const ProjectsContextDesktop = ({ | |||||||
|               : settings.projects.defaultProjectName.current |               : settings.projects.defaultProjectName.current | ||||||
|           ).trim() |           ).trim() | ||||||
|  |  | ||||||
|           const uniqueName = getUniqueProjectName(name, input.projects) |           if (doesProjectNameNeedInterpolated(name)) { | ||||||
|           await createNewProjectDirectory(uniqueName) |             const nextIndex = getNextProjectIndex(name, input.projects) | ||||||
|  |             name = interpolateProjectNameWithIndex(name, nextIndex) | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           await createNewProjectDirectory(name) | ||||||
|  |  | ||||||
|           return { |           return { | ||||||
|             message: `Successfully created "${uniqueName}"`, |             message: `Successfully created "${name}"`, | ||||||
|             name, |             name, | ||||||
|           } |           } | ||||||
|         }), |         }), | ||||||
|  | |||||||
| @ -1,58 +0,0 @@ | |||||||
| 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') |  | ||||||
|   }) |  | ||||||
| }) |  | ||||||
| @ -54,10 +54,8 @@ export function getNextProjectIndex( | |||||||
|   const matches = projects.map((project) => project.name?.match(regex)) |   const matches = projects.map((project) => project.name?.match(regex)) | ||||||
|   const indices = matches |   const indices = matches | ||||||
|     .filter(Boolean) |     .filter(Boolean) | ||||||
|     .map((match) => (match !== null ? match[1] : '-1')) |     .map((match) => match![1]) | ||||||
|     .map((maybeMatchIndex) => { |     .map(Number) | ||||||
|       return parseInt(maybeMatchIndex || '0', 10) |  | ||||||
|     }) |  | ||||||
|   const maxIndex = Math.max(...indices, -1) |   const maxIndex = Math.max(...indices, -1) | ||||||
|   return maxIndex + 1 |   return maxIndex + 1 | ||||||
| } | } | ||||||
| @ -85,33 +83,6 @@ export function doesProjectNameNeedInterpolated(projectName: string) { | |||||||
|   return projectName.includes(INDEX_IDENTIFIER) |   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) { | function escapeRegExpChars(string: string) { | ||||||
|   return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') |   return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') | ||||||
| } | } | ||||||
|  | |||||||
| @ -148,7 +148,7 @@ const Home = () => { | |||||||
|                 }} |                 }} | ||||||
|                 data-testid="home-new-file" |                 data-testid="home-new-file" | ||||||
|               > |               > | ||||||
|                 Create project |                 New project | ||||||
|               </ActionButton> |               </ActionButton> | ||||||
|             </div> |             </div> | ||||||
|             <div className="flex gap-2 items-center"> |             <div className="flex gap-2 items-center"> | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	