diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 9c381284..dc151302 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -8,13 +8,14 @@ on: jobs: prepare: name: Prepare - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} outputs: YARN_CACHE_DIR: ${{ steps.yarn-cache-dir.outputs.YARN_CACHE_DIR }} YARN_VERSION: ${{ steps.yarn-version.outputs.YARN_VERSION }} strategy: matrix: node-version: [16.x, 18.x, 20.x] + os: [ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} @@ -98,12 +99,13 @@ jobs: fi test: name: Test - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} needs: - prepare strategy: matrix: node-version: [16.x, 18.x, 20.x] + os: [ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} diff --git a/src/main.test.ts b/src/main.test.ts index 82dff548..ca96a812 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -41,6 +41,56 @@ describe('main', () => { }); }); + for (const [description, directoryPath] of [ + ['directories with spaces', '/path/to/my project'], + ['directories with quote', "/path/to/my 'project"], + ['directories with quotes', "/path/to/my 'proje'ct"], + ['directories with double-quote', '/path/to/my "project'], + ['directories with double-quotes', '/path/to/my "proje"ct'], + [ + 'directories with special characters', + '/path/~to/#my/!y \'\\"\\pr@j/e"ct', + ], + ]) { + const tempDirectoryPath = directoryPath.replace(/^\/path\//u, '/tmp/'); + const cwd = directoryPath.replace(/^\/path\//u, '/workdir/'); + it(`executes the monorepo workflow for ${description}`, async () => { + const project = buildMockProject({ + directoryPath, + isMonorepo: true, + }); + const stdout = fs.createWriteStream('/dev/null'); + const stderr = fs.createWriteStream('/dev/null'); + jest + .spyOn(initialParametersModule, 'determineInitialParameters') + .mockResolvedValue({ + project, + tempDirectoryPath, + reset: true, + releaseType: 'backport', + }); + const followMonorepoWorkflowSpy = jest + .spyOn(monorepoWorkflowOperations, 'followMonorepoWorkflow') + .mockResolvedValue(); + + await main({ + argv: [], + cwd, + stdout, + stderr, + }); + + expect(followMonorepoWorkflowSpy).toHaveBeenCalledWith({ + project, + tempDirectoryPath, + firstRemovingExistingReleaseSpecification: true, + releaseType: 'backport', + stdout, + stderr, + }); + }); + } + it('executes the polyrepo workflow if the project is within a polyrepo', async () => { const project = buildMockProject({ isMonorepo: false }); const stdout = fs.createWriteStream('/dev/null'); diff --git a/src/monorepo-workflow-operations.test.ts b/src/monorepo-workflow-operations.test.ts index e1c1e20d..29fa75ce 100644 --- a/src/monorepo-workflow-operations.test.ts +++ b/src/monorepo-workflow-operations.test.ts @@ -149,6 +149,7 @@ function buildMockEditor({ * @param args.releaseVersion - The new version that the release plan will * contain. * @param args.releaseType - The type of release. + * @param args.editorPath - Mocked path to the editor binary. * @returns Mock functions and other data that can be used in tests to make * assertions. */ @@ -162,6 +163,7 @@ async function setupFollowMonorepoWorkflow({ errorUponExecutingReleasePlan, releaseVersion = '1.0.0', releaseType = 'ordinary', + editorPath = '/some/editor', }: { sandbox: Sandbox; doesReleaseSpecFileExist: boolean; @@ -172,6 +174,7 @@ async function setupFollowMonorepoWorkflow({ errorUponExecutingReleasePlan?: Error; releaseVersion?: string; releaseType?: ReleaseType; + editorPath?: string; }) { const { determineEditorSpy, @@ -182,7 +185,7 @@ async function setupFollowMonorepoWorkflow({ executeReleasePlanSpy, captureChangesInReleaseBranchSpy, } = getDependencySpies(); - const editor = buildMockEditor(); + const editor = buildMockEditor({ path: editorPath }); const releaseSpecificationPath = path.join( sandbox.directoryPath, 'RELEASE_SPEC.yml', @@ -368,6 +371,50 @@ describe('monorepo-workflow-operations', () => { }); }); + for (const [description, editorPath] of [ + ['editor path with spaces', '/path/to/my editor'], + ['editor path with quote', "/path/to/my 'editor"], + ['editor path with quotes', "/path/to/my 'proje'ct"], + ['editor path with double-quote', '/path/to/my "editor'], + ['editor path with double-quotes', '/path/to/my "edi"tor'], + [ + 'editor path with special characters', + '/path/~to/#my/!y \'\\"\\e@i/0"r', + ], + ]) { + it(`can edit successfully with ${description}`, async () => { + await withSandbox(async (sandbox) => { + const { + project, + stdout, + stderr, + executeReleasePlanSpy, + releasePlan, + } = await setupFollowMonorepoWorkflow({ + sandbox, + doesReleaseSpecFileExist: false, + isEditorAvailable: true, + editorPath, + }); + + await followMonorepoWorkflow({ + project, + tempDirectoryPath: sandbox.directoryPath, + firstRemovingExistingReleaseSpecification: false, + releaseType: 'ordinary', + stdout, + stderr, + }); + + expect(executeReleasePlanSpy).toHaveBeenCalledWith( + project, + releasePlan, + stderr, + ); + }); + }); + } + it('creates a new branch named after the generated release version if editing, validating, and executing the release spec succeeds', async () => { await withSandbox(async (sandbox) => { const {