diff --git a/jest.config.js b/jest.config.js index 292a10c7..b0fbb7dc 100644 --- a/jest.config.js +++ b/jest.config.js @@ -19,6 +19,10 @@ module.exports = { }, moduleFileExtensions: ['ts', 'js'], setupFiles: ['/test/jest.setup.ts'], + testMatch: [ + '/src/**/*.spec.ts', + '/test/scenarios/**/*.spec.ts', + ], transform: { '^.+\\.ts$': ['ts-jest', { isolatedModules: true }], }, diff --git a/package.json b/package.json index 8574e033..309a1c2b 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,6 @@ "format:source": "prettier --write .", "lint": "eslint --ext .ts .", "prepack": "yarn build", - "test": "jest src" + "test": "jest src test" } } diff --git a/src/bin-fix-mismatches/fix-mismatches.spec.ts b/src/bin-fix-mismatches/fix-mismatches.spec.ts deleted file mode 100644 index e6346cde..00000000 --- a/src/bin-fix-mismatches/fix-mismatches.spec.ts +++ /dev/null @@ -1,204 +0,0 @@ -import 'expect-more-jest'; -import { customNameAndVersionMismatch } from '../../test/scenarios/custom-name-and-version-mismatch'; -import { customVersionMismatch } from '../../test/scenarios/custom-version-mismatch'; -import { customVersionsByNameMismatch } from '../../test/scenarios/custom-versions-by-name-mismatch'; -import { dependencyIsBanned } from '../../test/scenarios/dependency-is-banned'; -import { dependencyIsPinned } from '../../test/scenarios/dependency-is-pinned'; -import { dependentDoesNotMatchNestedWorkspaceVersion } from '../../test/scenarios/dependent-does-not-match-nested-workspace-version'; -import { dependentDoesNotMatchNpmOverrideVersion } from '../../test/scenarios/dependent-does-not-match-npm-override-version'; -import { dependentDoesNotMatchPnpmOverrideVersion } from '../../test/scenarios/dependent-does-not-match-pnpm-override-version'; -import { dependentDoesNotMatchWorkspaceVersion } from '../../test/scenarios/dependent-does-not-match-workspace-version'; -import { matchingUnsupportedVersions } from '../../test/scenarios/matching-unsupported-versions'; -import { mismatchingUnsupportedVersions } from '../../test/scenarios/mismatching-unsupported-versions'; -import { unusedCustomType } from '../../test/scenarios/unused-custom-type'; -import { useHighestVersion } from '../../test/scenarios/use-highest-version'; -import { versionIsIgnored } from '../../test/scenarios/version-is-ignored'; -import { fixMismatchesCli } from './fix-mismatches-cli'; - -describe('fixMismatches', () => { - afterEach(() => { - jest.resetAllMocks(); - }); - - describe('when dependencies are installed with different versions', () => { - describe('when the dependency is a package maintained in this workspace', () => { - describe('when using a typical workspace', () => { - it('warns about the workspace version', () => { - const scenario = dependentDoesNotMatchWorkspaceVersion(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - scenario.files['packages/b/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - scenario.files['packages/b/package.json'].logEntryWhenChanged, - scenario.files['packages/c/package.json'].logEntryWhenUnchanged, - ]); - }); - }); - - describe('when using nested workspaces', () => { - it('warns about the workspace version', () => { - const scenario = dependentDoesNotMatchNestedWorkspaceVersion(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['workspaces/a/packages/a/package.json'] - .diskWriteWhenChanged, - scenario.files['workspaces/b/packages/b/package.json'] - .diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['workspaces/a/packages/a/package.json'] - .logEntryWhenChanged, - scenario.files['workspaces/b/packages/b/package.json'] - .logEntryWhenChanged, - scenario.files['workspaces/b/packages/c/package.json'] - .logEntryWhenUnchanged, - ]); - }); - }); - - describe('when using custom types', () => { - it('ignores the mismatch in the custom location if it has been filtered out', () => { - const scenario = unusedCustomType(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync).not.toHaveBeenCalled(); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenUnchanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - ]); - }); - - it('fixes "versionsByName" mismatches in custom locations', () => { - const scenario = customVersionsByNameMismatch(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - ]); - }); - - it('fixes "name@version" mismatches in custom locations', () => { - const scenario = customNameAndVersionMismatch(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - ]); - }); - - it('fixes "version" mismatches in custom locations', () => { - const scenario = customVersionMismatch(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - ]); - }); - }); - }); - - it('skips mismatched versions which syncpack cannot fix', () => { - const scenario = mismatchingUnsupportedVersions(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync).not.toHaveBeenCalled(); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenUnchanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - scenario.files['packages/c/package.json'].logEntryWhenUnchanged, - scenario.files['packages/d/package.json'].logEntryWhenUnchanged, - ]); - }); - - it('skips matching versions which syncpack cannot fix anyway', () => { - const scenario = matchingUnsupportedVersions(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync).not.toHaveBeenCalled(); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenUnchanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - ]); - }); - - it('removes banned/disallowed dependencies', () => { - const scenario = dependencyIsBanned(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/b/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenUnchanged, - scenario.files['packages/b/package.json'].logEntryWhenChanged, - ]); - }); - - it('does not consider versions of ignored dependencies', () => { - const scenario = versionIsIgnored(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync).not.toHaveBeenCalled(); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenUnchanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - ]); - }); - - it('replaces mismatching npm overrides', () => { - const scenario = dependentDoesNotMatchNpmOverrideVersion(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - ]); - }); - - it('replaces mismatching pnpm overrides', () => { - const scenario = dependentDoesNotMatchPnpmOverrideVersion(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - ]); - }); - - it('synchronises pinned versions', () => { - const scenario = dependencyIsPinned(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - ]); - }); - - it('uses the highest installed version', () => { - const scenario = useHighestVersion(); - fixMismatchesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - scenario.files['packages/c/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - scenario.files['packages/c/package.json'].logEntryWhenChanged, - ]); - }); - }); -}); diff --git a/src/bin-format/format.spec.ts b/src/bin-format/format.spec.ts deleted file mode 100644 index b73362c4..00000000 --- a/src/bin-format/format.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import 'expect-more-jest'; -import { githubShorthand } from '../../test/scenarios/github-shorthand'; -import { protectedShorthand } from '../../test/scenarios/protected-shorthand'; -import { shorthand } from '../../test/scenarios/shorthand'; -import { sortArrayProps } from '../../test/scenarios/sort-array-props'; -import { sortFirst } from '../../test/scenarios/sort-first'; -import { sortObjectProps } from '../../test/scenarios/sort-object-props'; -import { formatCli } from './format-cli'; - -describe('format', () => { - it('sorts array properties alphabetically by value', () => { - const scenario = sortArrayProps(); - formatCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - ]); - }); - it('sorts object properties alphabetically by key', () => { - const scenario = sortObjectProps(); - formatCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - ]); - }); - it('sorts named properties first, then the rest alphabetically', () => { - const scenario = sortFirst(); - formatCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - ]); - }); - it('uses shorthand format for "bugs" and "repository"', () => { - const scenario = shorthand(); - formatCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - ]); - }); - it('retains long form format for "repository" when directory property used', () => { - const scenario = protectedShorthand(); - formatCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenUnchanged, - ]); - }); - it('uses github shorthand format for "repository"', () => { - const scenario = githubShorthand(); - formatCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - ]); - }); -}); diff --git a/src/bin-lint-semver-ranges/lint-semver-ranges-cli.spec.ts b/src/bin-lint-semver-ranges/lint-semver-ranges-cli.spec.ts deleted file mode 100644 index 817c7bba..00000000 --- a/src/bin-lint-semver-ranges/lint-semver-ranges-cli.spec.ts +++ /dev/null @@ -1,77 +0,0 @@ -import 'expect-more-jest'; -import { normalize } from 'path'; -import { customTypesAndSemverGroups } from '../../test/scenarios/custom-types-and-semver-groups'; -import { semverIsIgnored } from '../../test/scenarios/semver-is-ignored'; -import { semverRangesDoNotMatchConfig } from '../../test/scenarios/semver-ranges-do-not-match-config'; -import { semverRangesDoNotMatchConfigWildcard } from '../../test/scenarios/semver-ranges-do-not-match-config-wildcard'; -import { lintSemverRangesCli } from './lint-semver-ranges-cli'; - -describe('lintSemverRanges', () => { - beforeEach(() => { - jest.restoreAllMocks(); - }); - - it('lists versions with ranges which do not match the project config', () => { - const scenario = semverRangesDoNotMatchConfig(); - const a = normalize('packages/a/package.json'); - lintSemverRangesCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - ['✘ b'], - [` 0.1.0 → ~0.1.0 in devDependencies of ${a}`], - ['✘ c'], - [` 0.1.0 → ~0.1.0 in overrides of ${a}`], - ['✘ d'], - [` 0.1.0 → ~0.1.0 in pnpm.overrides of ${a}`], - ['✘ e'], - [` 0.1.0 → ~0.1.0 in peerDependencies of ${a}`], - ['✘ f'], - [` 0.1.0 → ~0.1.0 in resolutions of ${a}`], - ]); - }); - - it('ensures wildcard versions are supported', () => { - const scenario = semverRangesDoNotMatchConfigWildcard(); - const a = normalize('packages/a/package.json'); - lintSemverRangesCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - ['✘ b'], - [` 0.1.0 → * in devDependencies of ${a}`], - ['✘ c'], - [` 0.1.0 → * in overrides of ${a}`], - ['✘ d'], - [` 0.1.0 → * in pnpm.overrides of ${a}`], - ['✘ e'], - [` 0.1.0 → * in peerDependencies of ${a}`], - ['✘ f'], - [` 0.1.0 → * in resolutions of ${a}`], - ]); - }); - - it('does not include ignored dependencies in its output', () => { - const scenario = semverIsIgnored(); - const a = normalize('packages/a/package.json'); - lintSemverRangesCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - [expect.stringMatching(/Default Semver Group/)], - ['✘ foo'], - [` 0.1.0 → ~0.1.0 in dependencies of ${a}`], - ]); - }); - - it('list issues from multiple custom types and semver groups together', () => { - const scenario = customTypesAndSemverGroups(); - const a = normalize('packages/a/package.json'); - lintSemverRangesCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - [expect.stringMatching(/Semver Group 1/)], - ['✘ node'], - [` 16.16.0 → >=16.16.0 in engines.node of ${a}`], - [expect.stringMatching(/Semver Group 2/)], - ['✘ npm'], - [` 7.24.2 → ^7.24.2 in engines.npm of ${a}`], - [expect.stringMatching(/Semver Group 3/)], - ['✘ yarn'], - [` ~2.0.0 → 2.0.0 in packageManager of ${a}`], - ]); - }); -}); diff --git a/src/bin-list-mismatches/list-mismatches-cli.spec.ts b/src/bin-list-mismatches/list-mismatches-cli.spec.ts deleted file mode 100644 index 161a4f37..00000000 --- a/src/bin-list-mismatches/list-mismatches-cli.spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -import 'expect-more-jest'; -import { normalize } from 'path'; -import { dependencyIsBanned } from '../../test/scenarios/dependency-is-banned'; -import { dependencyIsPinned } from '../../test/scenarios/dependency-is-pinned'; -import { dependentDoesNotMatchNestedWorkspaceVersion } from '../../test/scenarios/dependent-does-not-match-nested-workspace-version'; -import { dependentDoesNotMatchWorkspaceVersion } from '../../test/scenarios/dependent-does-not-match-workspace-version'; -import { matchingUnsupportedVersions } from '../../test/scenarios/matching-unsupported-versions'; -import { mismatchingUnsupportedVersions } from '../../test/scenarios/mismatching-unsupported-versions'; -import { useHighestVersion } from '../../test/scenarios/use-highest-version'; -import { versionIsIgnored } from '../../test/scenarios/version-is-ignored'; -import { ICON } from '../constants'; -import { listMismatchesCli } from './list-mismatches-cli'; - -describe('listMismatches', () => { - beforeEach(() => { - jest.restoreAllMocks(); - }); - - describe('when dependencies are installed with different versions', () => { - describe('when the dependency is a package maintained in this workspace', () => { - describe('when using a typical workspace', () => { - it('warns about the workspace version', () => { - const scenario = dependentDoesNotMatchWorkspaceVersion(); - const a = 'packages/a/package.json'; - const b = 'packages/b/package.json'; - const c = 'packages/c/package.json'; - listMismatchesCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual( - [ - [ - `${ICON.cross} c 0.0.1 is developed in this repo at ${normalize( - c, - )}`, - ], - [` 0.1.0 in dependencies of ${normalize(a)}`], - [` 0.2.0 in devDependencies of ${normalize(b)}`], - ].map(([msg]) => [normalize(msg)]), - ); - expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); - }); - }); - - describe('when using nested workspaces', () => { - it('warns about the workspace version', () => { - const scenario = dependentDoesNotMatchNestedWorkspaceVersion(); - const bc = 'workspaces/b/packages/c/package.json'; - const aa = 'workspaces/a/packages/a/package.json'; - const bb = 'workspaces/b/packages/b/package.json'; - listMismatchesCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual( - [ - [ - `${ICON.cross} c 0.0.1 is developed in this repo at ${normalize( - bc, - )}`, - ], - [` 0.1.0 in dependencies of ${normalize(aa)}`], - [` 0.2.0 in devDependencies of ${normalize(bb)}`], - ].map(([msg]) => [normalize(msg)]), - ); - expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); - }); - }); - }); - - it('lists mismatched versions which syncpack cannot fix', () => { - const scenario = mismatchingUnsupportedVersions(); - const a = 'packages/a/package.json'; - const b = 'packages/b/package.json'; - const c = 'packages/c/package.json'; - const d = 'packages/d/package.json'; - listMismatchesCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - [`${ICON.cross} foo has mismatched versions which syncpack cannot fix`], - [` link:vendor/foo-0.1.0 in dependencies of ${normalize(a)}`], - [` workspace:* in dependencies of ${normalize(b)}`], - [` 0.3.0 in dependencies of ${normalize(c)}`], - [` 0.2.0 in dependencies of ${normalize(d)}`], - ]); - expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); - }); - - it('does not list matching versions which syncpack cannot fix anyway', () => { - const scenario = matchingUnsupportedVersions(); - listMismatchesCli(scenario.config, scenario.disk); - expect(scenario.log).not.toHaveBeenCalled(); - expect(scenario.disk.process.exit).not.toHaveBeenCalled(); - }); - - it('removes banned/disallowed dependencies', () => { - const scenario = dependencyIsBanned(); - const b = 'packages/b/package.json'; - listMismatchesCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - [expect.stringMatching(/Version Group 1/)], - [`${ICON.cross} bar is banned in this version group`], - [` 0.2.0 in dependencies of ${normalize(b)}`], - ]); - expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); - }); - - it('does not mention ignored dependencies', () => { - const scenario = versionIsIgnored(); - listMismatchesCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toBeEmptyArray(); - expect(scenario.disk.process.exit).not.toHaveBeenCalled(); - }); - - it('synchronises pinned versions', () => { - const scenario = dependencyIsPinned(); - const a = 'packages/a/package.json'; - listMismatchesCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - [expect.stringMatching(/Version Group 1/)], - [`${ICON.cross} bar is pinned in this version group at 2.2.2`], - [` 0.2.0 in dependencies of ${normalize(a)}`], - ]); - }); - - it('uses the highest installed version', () => { - const scenario = useHighestVersion(); - const a = 'packages/a/package.json'; - const c = 'packages/c/package.json'; - listMismatchesCli(scenario.config, scenario.disk); - - expect(scenario.log.mock.calls).toEqual([ - [`${ICON.cross} bar 0.3.0 is the highest valid semver version in use`], - [` 0.2.0 in dependencies of ${normalize(a)}`], - [` 0.1.0 in dependencies of ${normalize(c)}`], - ]); - }); - }); -}); diff --git a/src/bin-list/list-cli.spec.ts b/src/bin-list/list-cli.spec.ts deleted file mode 100644 index d94a166c..00000000 --- a/src/bin-list/list-cli.spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -import 'expect-more-jest'; -import { dependencyIsBanned } from '../../test/scenarios/dependency-is-banned'; -import { dependencyIsPinned } from '../../test/scenarios/dependency-is-pinned'; -import { dependentDoesNotMatchNestedWorkspaceVersion } from '../../test/scenarios/dependent-does-not-match-nested-workspace-version'; -import { dependentDoesNotMatchNpmOverrideVersion } from '../../test/scenarios/dependent-does-not-match-npm-override-version'; -import { dependentDoesNotMatchPnpmOverrideVersion } from '../../test/scenarios/dependent-does-not-match-pnpm-override-version'; -import { dependentDoesNotMatchWorkspaceVersion } from '../../test/scenarios/dependent-does-not-match-workspace-version'; -import type { TestScenario } from '../../test/scenarios/lib/create-scenario'; -import { matchingUnsupportedVersions } from '../../test/scenarios/matching-unsupported-versions'; -import { mismatchingUnsupportedVersions } from '../../test/scenarios/mismatching-unsupported-versions'; -import { useHighestVersion } from '../../test/scenarios/use-highest-version'; -import { versionIsIgnored } from '../../test/scenarios/version-is-ignored'; -import { listCli } from './list-cli'; - -import { mismatchIsFilteredOut } from '../../test/scenarios/mismatch-is-filtered-out'; - -describe('list', () => { - beforeEach(() => { - jest.restoreAllMocks(); - }); - - describe('when dependencies are installed with different versions', () => { - describe('when the dependency is a package maintained in this workspace', () => { - const variants: [string, () => TestScenario][] = [ - [ - 'when using a typical workspace', - dependentDoesNotMatchWorkspaceVersion, - ], - [ - 'when using nested workspaces', - dependentDoesNotMatchNestedWorkspaceVersion, - ], - ]; - variants.forEach(([context, getScenario]) => { - describe(`${context}`, () => { - it('warns about the workspace version', () => { - const scenario = getScenario(); - listCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - ['✘ c 0.0.1, 0.1.0, 0.2.0'], - ]); - expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); - }); - }); - }); - }); - - it('lists mismatched versions which syncpack cannot fix', () => { - const scenario = mismatchingUnsupportedVersions(); - listCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - [ - '✘ foo has mismatched versions which syncpack cannot fix: 0.2.0, 0.3.0, link:vendor/foo-0.1.0, workspace:*', - ], - ]); - expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); - }); - - it('lists matching versions which syncpack cannot fix anyway', () => { - const scenario = matchingUnsupportedVersions(); - listCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([['- foo workspace:*']]); - expect(scenario.disk.process.exit).not.toHaveBeenCalled(); - }); - - it('lists mismatching npm overrides', () => { - const scenario = dependentDoesNotMatchNpmOverrideVersion(); - listCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([['✘ c 0.1.0, 0.2.0']]); - expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); - }); - - it('lists mismatching pnpm overrides', () => { - const scenario = dependentDoesNotMatchPnpmOverrideVersion(); - listCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([['✘ c 0.1.0, 0.2.0']]); - expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); - }); - - it('removes banned/disallowed dependencies', () => { - const scenario = dependencyIsBanned(); - listCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - [expect.stringMatching(/Version Group 1/)], - ['✘ bar is banned in this version group'], - [expect.stringMatching(/Default Version Group/)], - ['- foo 0.1.0'], - ]); - expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); - }); - - it('mentions ignored dependencies', () => { - const scenario = versionIsIgnored(); - listCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - [expect.stringMatching(/Version Group 1/)], - ['- bar is ignored in this version group'], - [expect.stringMatching(/Default Version Group/)], - ['- foo 0.1.0'], - ]); - expect(scenario.disk.process.exit).not.toHaveBeenCalled(); - }); - - it('lists mismatching pinned versions', () => { - const scenario = dependencyIsPinned(); - listCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - [expect.stringMatching(/Version Group 1/)], - ['✘ bar is pinned to 2.2.2 in this version group'], - ]); - expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); - }); - - it('uses the highest installed version', () => { - const scenario = useHighestVersion(); - listCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([['✘ bar 0.1.0, 0.2.0, 0.3.0']]); - expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); - }); - - it('ignores mismatches which do not match applied filter', () => { - const scenario = mismatchIsFilteredOut(); - listCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([['- d 1.1.1']]); - expect(scenario.disk.process.exit).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/bin-set-semver-ranges/set-semver-ranges-cli.spec.ts b/src/bin-set-semver-ranges/set-semver-ranges-cli.spec.ts deleted file mode 100644 index 73683a05..00000000 --- a/src/bin-set-semver-ranges/set-semver-ranges-cli.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import 'expect-more-jest'; -import { customTypesAndSemverGroups } from '../../test/scenarios/custom-types-and-semver-groups'; -import { issue84Reproduction } from '../../test/scenarios/issue84-reproduction'; -import { matchingUnsupportedVersions } from '../../test/scenarios/matching-unsupported-versions'; -import { semverIsIgnored } from '../../test/scenarios/semver-is-ignored'; -import { semverRangesDoNotMatchConfig } from '../../test/scenarios/semver-ranges-do-not-match-config'; -import { setSemverRangesCli } from './set-semver-ranges-cli'; - -describe('setSemverRanges', () => { - beforeEach(() => { - jest.restoreAllMocks(); - }); - - it('sets all versions to use the supplied range', () => { - const scenario = semverRangesDoNotMatchConfig(); - setSemverRangesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - ]); - }); - - it('leaves ignored dependencies unchanged', () => { - const scenario = semverIsIgnored(); - setSemverRangesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - ]); - }); - - it('leaves non-semver versions unchanged', () => { - const scenario = matchingUnsupportedVersions(); - setSemverRangesCli(scenario.config, scenario.disk); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenUnchanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - ]); - expect(scenario.disk.process.exit).not.toHaveBeenCalled(); - }); - - it('fixes issue 84', () => { - const scenario = issue84Reproduction(); - setSemverRangesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - scenario.files['packages/b/package.json'].logEntryWhenUnchanged, - ]); - }); - - it('fixes multiple custom types and semver groups together', () => { - const scenario = customTypesAndSemverGroups(); - setSemverRangesCli(scenario.config, scenario.disk); - expect(scenario.disk.writeFileSync.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].diskWriteWhenChanged, - ]); - expect(scenario.log.mock.calls).toEqual([ - scenario.files['packages/a/package.json'].logEntryWhenChanged, - ]); - }); -}); diff --git a/src/get-context/get-config/schema/base-group.ts b/src/get-context/get-config/schema/base-group.ts index ec45eb4b..5d13ec28 100644 --- a/src/get-context/get-config/schema/base-group.ts +++ b/src/get-context/get-config/schema/base-group.ts @@ -3,6 +3,6 @@ import { z } from 'zod'; export const baseGroupFields = { dependencies: z.array(z.string()).min(1), dependencyTypes: z.array(z.string()).default([]), - label: z.string().default(''), + label: z.string().default('').optional(), packages: z.array(z.string()).min(1), }; diff --git a/src/get-context/get-context.spec.ts b/src/get-context/get-context.spec.ts index 45227ee2..c56f8fe6 100644 --- a/src/get-context/get-context.spec.ts +++ b/src/get-context/get-context.spec.ts @@ -13,11 +13,13 @@ describe('getContext', () => { DEFAULT_CONFIG.source, ); }); + it('uses value from config when no CLI options are set', () => { const disk = mockDisk(); disk.readConfigFileSync.mockReturnValue({ source: ['foo'] }); expect(getContext({}, disk)).toHaveProperty('source', ['foo']); }); + it('uses value from CLI when config and CLI options are set', () => { const disk = mockDisk(); disk.readConfigFileSync.mockReturnValue({ source: ['foo'] }); @@ -25,6 +27,7 @@ describe('getContext', () => { 'bar', ]); }); + it('combines defaults, values from CLI options, and config', () => { const disk = mockDisk(); disk.readConfigFileSync.mockReturnValue({ source: ['foo'] }); @@ -36,6 +39,7 @@ describe('getContext', () => { }), ); }); + describe('only available in config files', () => { it('merges semverGroups', () => { const disk = mockDisk(); @@ -65,6 +69,7 @@ describe('getContext', () => { }), ]); }); + it('merges versionGroups', () => { const disk = mockDisk(); disk.readConfigFileSync.mockReturnValue({ @@ -89,6 +94,7 @@ describe('getContext', () => { }); }); }); + describe('packageJsonFiles', () => { describe('when --source cli options are given', () => { describe('for a single package.json file', () => { @@ -116,6 +122,7 @@ describe('getContext', () => { ); }); }); + describe('for a pattern that matches nothing', () => { it('returns an empty array', () => { const disk = mockDisk(); @@ -128,6 +135,7 @@ describe('getContext', () => { }); }); }); + describe('when no --source cli options are given', () => { it('performs a default search', () => { const disk = mockDisk(); @@ -137,6 +145,7 @@ describe('getContext', () => { ['packages/*/package.json'], ]); }); + describe('when yarn workspaces are defined', () => { describe('as an array', () => { it('resolves yarn workspace packages', () => { @@ -153,6 +162,7 @@ describe('getContext', () => { ]); }); }); + describe('as an object', () => { it('resolves yarn workspace packages', () => { const filePath = join(CWD, 'package.json'); @@ -169,6 +179,7 @@ describe('getContext', () => { }); }); }); + describe('when yarn workspaces are not defined', () => { describe('when lerna.json is defined', () => { it('resolves lerna packages', () => { @@ -189,6 +200,7 @@ describe('getContext', () => { ]); }); }); + describe('when lerna.json is not defined', () => { describe('when pnpm config is present', () => { describe('when pnpm workspaces are defined', () => { @@ -206,6 +218,7 @@ describe('getContext', () => { ]); }); }); + describe('when pnpm config is invalid', () => { it('performs a default search', () => { const filePath = join(CWD, 'package.json'); diff --git a/src/get-context/get-groups/index.ts b/src/get-context/get-groups/index.ts index 1798e0ab..b6ebadc9 100644 --- a/src/get-context/get-groups/index.ts +++ b/src/get-context/get-groups/index.ts @@ -35,6 +35,7 @@ export function getGroups( }); }); + /* istanbul ignore if */ if (process.env.SYNCPACK_VERBOSE) { groupNames.forEach((key) => { groupsByName[key].forEach((group, i) => { diff --git a/src/lib/disk.spec.ts b/src/lib/disk.spec.ts index a09bf62a..905c2aea 100644 --- a/src/lib/disk.spec.ts +++ b/src/lib/disk.spec.ts @@ -17,6 +17,7 @@ beforeEach(() => { }); const configPath = '/path/to/.syncpackrc'; + describe('readConfigFileSync', () => { it('searches parent directories when no file path is provided', () => { const client = require('cosmiconfig').cosmiconfigSync(); @@ -38,6 +39,7 @@ describe('readConfigFileSync', () => { expect(client.search).not.toHaveBeenCalled(); }); }); + describe('when the file can not be found', () => { it('returns an empty object', () => { const client = require('cosmiconfig').cosmiconfigSync(); diff --git a/src/lib/log.ts b/src/lib/log.ts index 330174b6..5db3819e 100644 --- a/src/lib/log.ts +++ b/src/lib/log.ts @@ -6,6 +6,7 @@ import type { SemverGroup } from '../get-context/get-groups/semver-group'; import type { VersionGroup } from '../get-context/get-groups/version-group'; export function verbose(...values: unknown[]): void { + /* istanbul ignore if */ if (process.env.SYNCPACK_VERBOSE) { console.info( chalk.yellow(ICON.debug), diff --git a/test/scenarios/create-scenario.ts b/test/scenarios/create-scenario.ts deleted file mode 100644 index 03aeb4c4..00000000 --- a/test/scenarios/create-scenario.ts +++ /dev/null @@ -1,100 +0,0 @@ -import minimatch from 'minimatch'; -import { join, normalize } from 'path'; -import { CWD } from '../../src/constants'; -import type { JsonFile } from '../../src/get-context/get-package-json-files/get-patterns/read-json-safe'; -import type { PackageJson } from '../../src/get-context/get-package-json-files/package-json-file'; -import type { Syncpack } from '../../src/types'; -import type { MockDisk } from '../mock-disk'; -import { mockDisk } from '../mock-disk'; - -interface MockedFile { - absolutePath: string; - after: JsonFile; - before: JsonFile; - diskWriteWhenChanged: [string, string]; - id: string; - logEntryWhenChanged: [any, any]; - logEntryWhenUnchanged: [any, any]; - relativePath: string; -} - -export interface TestScenario { - config: Partial; - disk: MockDisk; - log: jest.SpyInstance; - files: Record; -} - -export function createScenario( - fileMocks: { - path: string; - before: JsonFile; - after: JsonFile; - }[], - config: Partial, -): TestScenario { - jest.clearAllMocks(); - const disk = mockDisk(); - const log = jest.spyOn(console, 'log').mockImplementation(() => undefined); - // resolve all paths - const mockedFiles: MockedFile[] = fileMocks.map((file) => { - const absolutePath = join(CWD, file.path); - const relativePath = normalize(file.path); - return { - absolutePath, - after: { - ...file.after, - filePath: absolutePath, - }, - before: { - ...file.before, - filePath: absolutePath, - }, - diskWriteWhenChanged: [ - expect.stringContaining(relativePath), - file.after.json, - ], - id: file.path, - logEntryWhenChanged: [ - expect.stringMatching(/✓/), - expect.stringContaining(relativePath), - ], - logEntryWhenUnchanged: [ - expect.stringMatching(/-/), - expect.stringContaining(relativePath), - ], - relativePath, - }; - }); - // mock file system - disk.readFileSync.mockImplementation((filePath): string | undefined => { - return mockedFiles.find((file) => { - return normalize(filePath) === normalize(file.absolutePath); - })?.before?.json; - }); - // mock globs - disk.globSync.mockImplementation((pattern): string[] => { - return mockedFiles - .filter((file) => { - return minimatch( - normalize(file.absolutePath), - toPosix(join(CWD, pattern)), - ); - }) - .map((file) => normalize(file.absolutePath)); - }); - // return API - return { - config, - disk, - log, - files: mockedFiles.reduce((memo, file) => { - memo[file.id] = file; - return memo; - }, {} as Record), - }; -} - -function toPosix(value: string): string { - return value.replace('C:', '').replace(/\\/g, '/'); -} diff --git a/test/scenarios/custom-name-and-version-mismatch.spec.ts b/test/scenarios/custom-name-and-version-mismatch.spec.ts new file mode 100644 index 00000000..aa9da98d --- /dev/null +++ b/test/scenarios/custom-name-and-version-mismatch.spec.ts @@ -0,0 +1,58 @@ +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - .syncpackrc has a custom type to check the "packageManager" property. + * - A has yarn@2 + * - B has yarn@3 + * - A should be fixed to use yarn@3 + */ +describe('Custom name and version mismatch', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + otherProps: { packageManager: 'yarn@2.0.0' }, + }), + after: mockPackage('a', { + otherProps: { packageManager: 'yarn@3.0.0' }, + }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { + otherProps: { packageManager: 'yarn@3.0.0' }, + }), + after: mockPackage('b', { + otherProps: { packageManager: 'yarn@3.0.0' }, + }), + }, + ], + { + customTypes: { + engines: { + strategy: 'name@version', + path: 'packageManager', + }, + }, + }, + ); + } + + describe('fix-mismatches', () => { + it('fixes "name@version" mismatches in custom locations', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + ]); + }); + }); +}); diff --git a/test/scenarios/custom-name-and-version-mismatch.ts b/test/scenarios/custom-name-and-version-mismatch.ts deleted file mode 100644 index c2d8ee5b..00000000 --- a/test/scenarios/custom-name-and-version-mismatch.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - .syncpackrc has a custom type defined to check the "packageManager" property. - * - A has yarn@2 - * - B has yarn@3 - * - A should be fixed to use yarn@3 - */ -export function customNameAndVersionMismatch() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - otherProps: { packageManager: 'yarn@2.0.0' }, - }), - after: mockPackage('a', { - otherProps: { packageManager: 'yarn@3.0.0' }, - }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { - otherProps: { packageManager: 'yarn@3.0.0' }, - }), - after: mockPackage('b', { - otherProps: { packageManager: 'yarn@3.0.0' }, - }), - }, - ], - { - customTypes: { - engines: { - strategy: 'name@version', - path: 'packageManager', - }, - }, - }, - ); -} diff --git a/test/scenarios/custom-types-and-semver-groups.spec.ts b/test/scenarios/custom-types-and-semver-groups.spec.ts new file mode 100644 index 00000000..f0693680 --- /dev/null +++ b/test/scenarios/custom-types-and-semver-groups.spec.ts @@ -0,0 +1,109 @@ +import { normalize } from 'path'; +import { lintSemverRangesCli } from '../../src/bin-lint-semver-ranges/lint-semver-ranges-cli'; + +import { setSemverRangesCli } from '../../src/bin-set-semver-ranges/set-semver-ranges-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - .syncpackrc has multiple custom types defined to run on every package + * - Each semver group applies to one custom type + * - All of the semver groups should run and fix + */ +describe('Custom types and semver groups', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + otherProps: { + packageManager: 'yarn@~2.0.0', + engines: { + node: '16.16.0', + npm: '7.24.2', + }, + }, + }), + after: mockPackage('a', { + otherProps: { + packageManager: 'yarn@2.0.0', + engines: { + node: '>=16.16.0', + npm: '^7.24.2', + }, + }, + }), + }, + ], + { + customTypes: { + enginesNpm: { + path: 'engines.npm', + strategy: 'version', + }, + enginesNode: { + path: 'engines.node', + strategy: 'version', + }, + packageManager: { + path: 'packageManager', + strategy: 'name@version', + }, + }, + semverGroups: [ + { + dependencyTypes: ['enginesNode'], + dependencies: ['**'], + packages: ['**'], + range: '>=', + }, + { + dependencyTypes: ['enginesNpm'], + dependencies: ['**'], + packages: ['**'], + range: '^', + }, + { + dependencyTypes: ['packageManager'], + dependencies: ['**'], + packages: ['**'], + range: '', + }, + ], + }, + ); + } + + describe('lint-semver-ranges', () => { + it('list issues from multiple custom types and semver groups together', () => { + const scenario = getScenario(); + const a = normalize('packages/a/package.json'); + lintSemverRangesCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + [expect.stringMatching(/Semver Group 1/)], + ['✘ node'], + [` 16.16.0 → >=16.16.0 in engines.node of ${a}`], + [expect.stringMatching(/Semver Group 2/)], + ['✘ npm'], + [` 7.24.2 → ^7.24.2 in engines.npm of ${a}`], + [expect.stringMatching(/Semver Group 3/)], + ['✘ yarn'], + [` ~2.0.0 → 2.0.0 in packageManager of ${a}`], + ]); + }); + }); + + describe('set-semver-ranges', () => { + it('fixes multiple custom types and semver groups together', () => { + const scenario = getScenario(); + setSemverRangesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + ]); + }); + }); +}); diff --git a/test/scenarios/custom-types-and-semver-groups.ts b/test/scenarios/custom-types-and-semver-groups.ts deleted file mode 100644 index 3a4609a1..00000000 --- a/test/scenarios/custom-types-and-semver-groups.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - .syncpackrc has multiple custom types defined to run on every package - * - Each semver group applies to one custom type - * - All of the semver groups should run and fix - */ -export function customTypesAndSemverGroups() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - otherProps: { - packageManager: 'yarn@~2.0.0', - engines: { - node: '16.16.0', - npm: '7.24.2', - }, - }, - }), - after: mockPackage('a', { - otherProps: { - packageManager: 'yarn@2.0.0', - engines: { - node: '>=16.16.0', - npm: '^7.24.2', - }, - }, - }), - }, - ], - { - customTypes: { - enginesNpm: { - path: 'engines.npm', - strategy: 'version', - }, - enginesNode: { - path: 'engines.node', - strategy: 'version', - }, - packageManager: { - path: 'packageManager', - strategy: 'name@version', - }, - }, - semverGroups: [ - { - dependencyTypes: ['enginesNode'], - dependencies: ['**'], - packages: ['**'], - range: '>=', - }, - { - dependencyTypes: ['enginesNpm'], - dependencies: ['**'], - packages: ['**'], - range: '^', - }, - { - dependencyTypes: ['packageManager'], - dependencies: ['**'], - packages: ['**'], - range: '', - }, - ], - }, - ); -} diff --git a/test/scenarios/custom-version-mismatch.spec.ts b/test/scenarios/custom-version-mismatch.spec.ts new file mode 100644 index 00000000..d45b95da --- /dev/null +++ b/test/scenarios/custom-version-mismatch.spec.ts @@ -0,0 +1,78 @@ +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - .syncpackrc has a custom type defined to check the "somePlugin.version" property. + * - A has 2.0.0 + * - B has 3.0.0 + * - A should be fixed to use 3.0.0 + */ +describe('Custom version mismatch', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + otherProps: { somePlugin: { version: '2.0.0' } }, + }), + after: mockPackage('a', { + otherProps: { somePlugin: { version: '3.0.0' } }, + }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { + otherProps: { somePlugin: { version: '3.0.0' } }, + }), + after: mockPackage('b', { + otherProps: { somePlugin: { version: '3.0.0' } }, + }), + }, + ], + { + customTypes: { + engines: { + strategy: 'version', + path: 'somePlugin.version', + }, + }, + }, + ); + } + + describe('fix-mismatches', () => { + it('fixes "version" mismatches in custom locations', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/custom-version-mismatch.ts b/test/scenarios/custom-version-mismatch.ts deleted file mode 100644 index 131c5d2f..00000000 --- a/test/scenarios/custom-version-mismatch.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - .syncpackrc has a custom type defined to check the "somePlugin.version" property. - * - A has 2.0.0 - * - B has 3.0.0 - * - A should be fixed to use 3.0.0 - */ -export function customVersionMismatch() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - otherProps: { somePlugin: { version: '2.0.0' } }, - }), - after: mockPackage('a', { - otherProps: { somePlugin: { version: '3.0.0' } }, - }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { - otherProps: { somePlugin: { version: '3.0.0' } }, - }), - after: mockPackage('b', { - otherProps: { somePlugin: { version: '3.0.0' } }, - }), - }, - ], - { - customTypes: { - engines: { - strategy: 'version', - path: 'somePlugin.version', - }, - }, - }, - ); -} diff --git a/test/scenarios/custom-versions-by-name-mismatch.spec.ts b/test/scenarios/custom-versions-by-name-mismatch.spec.ts new file mode 100644 index 00000000..ad4b9ec2 --- /dev/null +++ b/test/scenarios/custom-versions-by-name-mismatch.spec.ts @@ -0,0 +1,78 @@ +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - .syncpackrc has a custom type defined to check the "engines" property. + * - A has node 14 + * - B has node 16 + * - A should be fixed to use 16 + */ +describe('Custom versions by name mismatch', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + otherProps: { engines: { node: '14.0.0' } }, + }), + after: mockPackage('a', { + otherProps: { engines: { node: '16.0.0' } }, + }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { + otherProps: { engines: { node: '16.0.0' } }, + }), + after: mockPackage('b', { + otherProps: { engines: { node: '14.0.0' } }, + }), + }, + ], + { + customTypes: { + engines: { + strategy: 'versionsByName', + path: 'engines', + }, + }, + }, + ); + } + + describe('fix-mismatches', () => { + it('fixes "versionsByName" mismatches in custom locations', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/custom-versions-by-name-mismatch.ts b/test/scenarios/custom-versions-by-name-mismatch.ts deleted file mode 100644 index 11f465f8..00000000 --- a/test/scenarios/custom-versions-by-name-mismatch.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - .syncpackrc has a custom type defined to check the "engines" property. - * - A has node 14 - * - B has node 16 - * - A should be fixed to use 16 - */ -export function customVersionsByNameMismatch() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - otherProps: { engines: { node: '14.0.0' } }, - }), - after: mockPackage('a', { - otherProps: { engines: { node: '16.0.0' } }, - }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { - otherProps: { engines: { node: '16.0.0' } }, - }), - after: mockPackage('b', { - otherProps: { engines: { node: '14.0.0' } }, - }), - }, - ], - { - customTypes: { - engines: { - strategy: 'versionsByName', - path: 'engines', - }, - }, - }, - ); -} diff --git a/test/scenarios/dependency-is-banned.spec.ts b/test/scenarios/dependency-is-banned.spec.ts new file mode 100644 index 00000000..b09409ca --- /dev/null +++ b/test/scenarios/dependency-is-banned.spec.ts @@ -0,0 +1,96 @@ +import { normalize } from 'path'; +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { listMismatchesCli } from '../../src/bin-list-mismatches/list-mismatches-cli'; +import { listCli } from '../../src/bin-list/list-cli'; +import { ICON } from '../../src/constants'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - A does not depend on `bar` + * - B does depend on `bar` + * - `bar` is banned in every package from being installed + * - `bar` should be removed from B + */ +describe('Dependency is banned', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { deps: ['foo@0.1.0'] }), + after: mockPackage('a', { deps: ['foo@0.1.0'] }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { deps: ['bar@0.2.0'] }), + after: mockPackage('b'), + }, + ], + { + versionGroups: [ + { + dependencies: ['bar'], + dependencyTypes: [], + packages: ['**'], + isBanned: true, + }, + ], + }, + ); + } + + describe('fix-mismatches', () => { + it('removes banned/disallowed dependencies', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/b/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenUnchanged, + scenario.files['packages/b/package.json'].logEntryWhenChanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + it('removes banned/disallowed dependencies', () => { + const scenario = getScenario(); + const b = 'packages/b/package.json'; + listMismatchesCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + [expect.stringMatching(/Version Group 1/)], + [`${ICON.cross} bar is banned in this version group`], + [` 0.2.0 in dependencies of ${normalize(b)}`], + ]); + expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); + }); + }); + + describe('list', () => { + it('removes banned/disallowed dependencies', () => { + const scenario = getScenario(); + listCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + [expect.stringMatching(/Version Group 1/)], + ['✘ bar is banned in this version group'], + [expect.stringMatching(/Default Version Group/)], + ['- foo 0.1.0'], + ]); + expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); + }); + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/dependency-is-banned.ts b/test/scenarios/dependency-is-banned.ts deleted file mode 100644 index 6af2047a..00000000 --- a/test/scenarios/dependency-is-banned.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - A does not depend on `bar` - * - B does depend on `bar` - * - `bar` is banned in every package from being installed - * - `bar` should be removed from B - */ -export function dependencyIsBanned() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['foo@0.1.0'] }), - after: mockPackage('a', { deps: ['foo@0.1.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['bar@0.2.0'] }), - after: mockPackage('b'), - }, - ], - { - versionGroups: [ - { - dependencies: ['bar'], - dependencyTypes: [], - packages: ['**'], - isBanned: true, - }, - ], - }, - ); -} diff --git a/test/scenarios/dependency-is-pinned.spec.ts b/test/scenarios/dependency-is-pinned.spec.ts new file mode 100644 index 00000000..8b6808d8 --- /dev/null +++ b/test/scenarios/dependency-is-pinned.spec.ts @@ -0,0 +1,82 @@ +import { normalize } from 'path'; +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { listMismatchesCli } from '../../src/bin-list-mismatches/list-mismatches-cli'; +import { listCli } from '../../src/bin-list/list-cli'; +import { ICON } from '../../src/constants'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** "bar" should always be 2.2.2 but is not */ +describe('Dependency is pinned', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { deps: ['bar@0.2.0'] }), + after: mockPackage('a', { deps: ['bar@2.2.2'] }), + }, + ], + { + versionGroups: [ + { + dependencies: ['bar'], + dependencyTypes: [], + packages: ['**'], + pinVersion: '2.2.2', + }, + ], + }, + ); + } + + describe('fix-mismatches', () => { + it('synchronises pinned versions', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + it('synchronises pinned versions', () => { + const scenario = getScenario(); + const a = 'packages/a/package.json'; + listMismatchesCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + [expect.stringMatching(/Version Group 1/)], + [`${ICON.cross} bar is pinned in this version group at 2.2.2`], + [` 0.2.0 in dependencies of ${normalize(a)}`], + ]); + }); + }); + + describe('list', () => { + it('lists mismatching pinned versions', () => { + const scenario = getScenario(); + listCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + [expect.stringMatching(/Version Group 1/)], + ['✘ bar is pinned to 2.2.2 in this version group'], + ]); + expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); + }); + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/dependency-is-pinned.ts b/test/scenarios/dependency-is-pinned.ts deleted file mode 100644 index e9ec84cd..00000000 --- a/test/scenarios/dependency-is-pinned.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** "bar" should always be 2.2.2 but is not */ -export function dependencyIsPinned() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['bar@0.2.0'] }), - after: mockPackage('a', { deps: ['bar@2.2.2'] }), - }, - ], - { - versionGroups: [ - { - dependencies: ['bar'], - dependencyTypes: [], - packages: ['**'], - pinVersion: '2.2.2', - }, - ], - }, - ); -} diff --git a/test/scenarios/dependent-does-not-match-nested-workspace-version.spec.ts b/test/scenarios/dependent-does-not-match-nested-workspace-version.spec.ts new file mode 100644 index 00000000..777f259c --- /dev/null +++ b/test/scenarios/dependent-does-not-match-nested-workspace-version.spec.ts @@ -0,0 +1,119 @@ +import { normalize } from 'path'; +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { listMismatchesCli } from '../../src/bin-list-mismatches/list-mismatches-cli'; +import { listCli } from '../../src/bin-list/list-cli'; +import { ICON } from '../../src/constants'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * Variation of `dependentDoesNotMatchWorkspaceVersion` in a nested workspace. + * + * - C is developed in this monorepo, its version is `0.0.1` + * - C's version is the single source of truth and should never be changed + * - A and B depend on C incorrectly and should be fixed + * - A, B, and C are in nested workspaces + * + * @see https://github.com/goldstack/goldstack/pull/170/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519R19 + * @see https://github.com/JamieMason/syncpack/pull/74 + * @see https://github.com/JamieMason/syncpack/issues/66 + */ +describe('Dependent does not match nested workspace version', () => { + function getScenario() { + return createScenario( + [ + { + path: 'workspaces/a/packages/a/package.json', + before: mockPackage('a', { deps: ['c@0.1.0'] }), + after: mockPackage('a', { deps: ['c@0.0.1'] }), + }, + { + path: 'workspaces/b/packages/b/package.json', + before: mockPackage('b', { devDeps: ['c@0.2.0'] }), + after: mockPackage('b', { devDeps: ['c@0.0.1'] }), + }, + { + path: 'workspaces/b/packages/c/package.json', + before: mockPackage('c', { + otherProps: { name: 'c', version: '0.0.1' }, + }), + after: mockPackage('c', { + otherProps: { name: 'c', version: '0.0.1' }, + }), + }, + ], + { + types: 'dev,prod,workspace', + source: [ + 'package.json', + 'workspaces/*/package.json', + 'workspaces/*/packages/*/package.json', + ], + }, + ); + } + + describe('fix-mismatches', () => { + it('warns about the workspace version', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['workspaces/a/packages/a/package.json'] + .diskWriteWhenChanged, + scenario.files['workspaces/b/packages/b/package.json'] + .diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['workspaces/a/packages/a/package.json'] + .logEntryWhenChanged, + scenario.files['workspaces/b/packages/b/package.json'] + .logEntryWhenChanged, + scenario.files['workspaces/b/packages/c/package.json'] + .logEntryWhenUnchanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + it('warns about the workspace version', () => { + const scenario = getScenario(); + const bc = 'workspaces/b/packages/c/package.json'; + const aa = 'workspaces/a/packages/a/package.json'; + const bb = 'workspaces/b/packages/b/package.json'; + listMismatchesCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual( + [ + [ + `${ICON.cross} c 0.0.1 is developed in this repo at ${normalize( + bc, + )}`, + ], + [` 0.1.0 in dependencies of ${normalize(aa)}`], + [` 0.2.0 in devDependencies of ${normalize(bb)}`], + ].map(([msg]) => [normalize(msg)]), + ); + expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); + }); + }); + + describe('list', () => { + it('warns about the workspace version', () => { + const scenario = getScenario(); + listCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([['✘ c 0.0.1, 0.1.0, 0.2.0']]); + expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); + }); + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/dependent-does-not-match-nested-workspace-version.ts b/test/scenarios/dependent-does-not-match-nested-workspace-version.ts deleted file mode 100644 index bfef8f22..00000000 --- a/test/scenarios/dependent-does-not-match-nested-workspace-version.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * Variation of `dependentDoesNotMatchWorkspaceVersion` in a nested workspace. - * - * - C is developed in this monorepo, its version is `0.0.1` - * - C's version is the single source of truth and should never be changed - * - A and B depend on C incorrectly and should be fixed - * - A, B, and C are in nested workspaces - * - * @see https://github.com/goldstack/goldstack/pull/170/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519R19 - * @see https://github.com/JamieMason/syncpack/pull/74 - * @see https://github.com/JamieMason/syncpack/issues/66 - */ -export function dependentDoesNotMatchNestedWorkspaceVersion() { - return createScenario( - [ - { - path: 'workspaces/a/packages/a/package.json', - before: mockPackage('a', { deps: ['c@0.1.0'] }), - after: mockPackage('a', { deps: ['c@0.0.1'] }), - }, - { - path: 'workspaces/b/packages/b/package.json', - before: mockPackage('b', { devDeps: ['c@0.2.0'] }), - after: mockPackage('b', { devDeps: ['c@0.0.1'] }), - }, - { - path: 'workspaces/b/packages/c/package.json', - before: mockPackage('c', { - otherProps: { name: 'c', version: '0.0.1' }, - }), - after: mockPackage('c', { - otherProps: { name: 'c', version: '0.0.1' }, - }), - }, - ], - { - types: 'dev,prod,workspace', - source: [ - 'package.json', - 'workspaces/*/package.json', - 'workspaces/*/packages/*/package.json', - ], - }, - ); -} diff --git a/test/scenarios/dependent-does-not-match-npm-override-version.spec.ts b/test/scenarios/dependent-does-not-match-npm-override-version.spec.ts new file mode 100644 index 00000000..b764ab15 --- /dev/null +++ b/test/scenarios/dependent-does-not-match-npm-override-version.spec.ts @@ -0,0 +1,71 @@ +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { listCli } from '../../src/bin-list/list-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - A has an npm override of C + * - B has an npm override of C + * - The versions do not match + * - The highest semver version wins + */ +describe('Dependent does not match npm override version', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { pnpmOverrides: ['c@0.1.0'] }), + after: mockPackage('a', { pnpmOverrides: ['c@0.2.0'] }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { pnpmOverrides: ['c@0.2.0'] }), + after: mockPackage('b', { pnpmOverrides: ['c@0.2.0'] }), + }, + ], + { + types: 'pnpmOverrides', + }, + ); + } + + describe('fix-mismatches', () => { + it('replaces mismatching npm overrides', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + it('lists mismatching npm overrides', () => { + const scenario = getScenario(); + listCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([['✘ c 0.1.0, 0.2.0']]); + expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); + }); + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/dependent-does-not-match-npm-override-version.ts b/test/scenarios/dependent-does-not-match-npm-override-version.ts deleted file mode 100644 index e3ebd761..00000000 --- a/test/scenarios/dependent-does-not-match-npm-override-version.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - A has an npm override of C - * - B has an npm override of C - * - The versions do not match - * - The highest semver version wins - */ -export function dependentDoesNotMatchNpmOverrideVersion() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { pnpmOverrides: ['c@0.1.0'] }), - after: mockPackage('a', { pnpmOverrides: ['c@0.2.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { pnpmOverrides: ['c@0.2.0'] }), - after: mockPackage('b', { pnpmOverrides: ['c@0.2.0'] }), - }, - ], - { - types: 'pnpmOverrides', - }, - ); -} diff --git a/test/scenarios/dependent-does-not-match-pnpm-override-version.spec.ts b/test/scenarios/dependent-does-not-match-pnpm-override-version.spec.ts new file mode 100644 index 00000000..8d9a340c --- /dev/null +++ b/test/scenarios/dependent-does-not-match-pnpm-override-version.spec.ts @@ -0,0 +1,71 @@ +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { listCli } from '../../src/bin-list/list-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - A has a pnpm override of C + * - B has a pnpm override of C + * - The versions do not match + * - The highest semver version wins + */ +describe('Dependent does not match pnpm override version', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { pnpmOverrides: ['c@0.1.0'] }), + after: mockPackage('a', { pnpmOverrides: ['c@0.2.0'] }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { pnpmOverrides: ['c@0.2.0'] }), + after: mockPackage('b', { pnpmOverrides: ['c@0.2.0'] }), + }, + ], + { + types: 'pnpmOverrides', + }, + ); + } + + describe('fix-mismatches', () => { + it('replaces mismatching pnpm overrides', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + it('lists mismatching pnpm overrides', () => { + const scenario = getScenario(); + listCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([['✘ c 0.1.0, 0.2.0']]); + expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); + }); + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/dependent-does-not-match-pnpm-override-version.ts b/test/scenarios/dependent-does-not-match-pnpm-override-version.ts deleted file mode 100644 index 4402c241..00000000 --- a/test/scenarios/dependent-does-not-match-pnpm-override-version.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - A has a pnpm override of C - * - B has a pnpm override of C - * - The versions do not match - * - The highest semver version wins - */ -export function dependentDoesNotMatchPnpmOverrideVersion() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { pnpmOverrides: ['c@0.1.0'] }), - after: mockPackage('a', { pnpmOverrides: ['c@0.2.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { pnpmOverrides: ['c@0.2.0'] }), - after: mockPackage('b', { pnpmOverrides: ['c@0.2.0'] }), - }, - ], - { - types: 'pnpmOverrides', - }, - ); -} diff --git a/test/scenarios/dependent-does-not-match-workspace-version.spec.ts b/test/scenarios/dependent-does-not-match-workspace-version.spec.ts new file mode 100644 index 00000000..3d5ce79f --- /dev/null +++ b/test/scenarios/dependent-does-not-match-workspace-version.spec.ts @@ -0,0 +1,101 @@ +import { normalize } from 'path'; +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { listMismatchesCli } from '../../src/bin-list-mismatches/list-mismatches-cli'; +import { listCli } from '../../src/bin-list/list-cli'; +import { ICON } from '../../src/constants'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - C is developed in this monorepo, its version is `0.0.1` + * - C's version is the single source of truth and should never be changed + * - A depends on C incorrectly and should be fixed + * - B depends on C incorrectly and should be fixed + */ +describe('Dependent does not match workspace version', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { deps: ['c@0.1.0'] }), + after: mockPackage('a', { deps: ['c@0.0.1'] }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { devDeps: ['c@0.2.0'] }), + after: mockPackage('b', { devDeps: ['c@0.0.1'] }), + }, + { + path: 'packages/c/package.json', + before: mockPackage('c', { + otherProps: { name: 'c', version: '0.0.1' }, + }), + after: mockPackage('c', { + otherProps: { name: 'c', version: '0.0.1' }, + }), + }, + ], + {}, + ); + } + + describe('fix-mismatches', () => { + it('warns about the workspace version', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + scenario.files['packages/b/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + scenario.files['packages/b/package.json'].logEntryWhenChanged, + scenario.files['packages/c/package.json'].logEntryWhenUnchanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + it('warns about the workspace version', () => { + const scenario = getScenario(); + const a = 'packages/a/package.json'; + const b = 'packages/b/package.json'; + const c = 'packages/c/package.json'; + listMismatchesCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual( + [ + [ + `${ICON.cross} c 0.0.1 is developed in this repo at ${normalize( + c, + )}`, + ], + [` 0.1.0 in dependencies of ${normalize(a)}`], + [` 0.2.0 in devDependencies of ${normalize(b)}`], + ].map(([msg]) => [normalize(msg)]), + ); + expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); + }); + }); + + describe('list', () => { + it('warns about the workspace version', () => { + const scenario = getScenario(); + listCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([['✘ c 0.0.1, 0.1.0, 0.2.0']]); + expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); + }); + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/dependent-does-not-match-workspace-version.ts b/test/scenarios/dependent-does-not-match-workspace-version.ts deleted file mode 100644 index d184beee..00000000 --- a/test/scenarios/dependent-does-not-match-workspace-version.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - C is developed in this monorepo, its version is `0.0.1` - * - C's version is the single source of truth and should never be changed - * - A depends on C incorrectly and should be fixed - * - B depends on C incorrectly and should be fixed - */ -export function dependentDoesNotMatchWorkspaceVersion() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['c@0.1.0'] }), - after: mockPackage('a', { deps: ['c@0.0.1'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { devDeps: ['c@0.2.0'] }), - after: mockPackage('b', { devDeps: ['c@0.0.1'] }), - }, - { - path: 'packages/c/package.json', - before: mockPackage('c', { - otherProps: { name: 'c', version: '0.0.1' }, - }), - after: mockPackage('c', { - otherProps: { name: 'c', version: '0.0.1' }, - }), - }, - ], - {}, - ); -} diff --git a/test/scenarios/github-shorthand.spec.ts b/test/scenarios/github-shorthand.spec.ts new file mode 100644 index 00000000..ecb9f5a0 --- /dev/null +++ b/test/scenarios/github-shorthand.spec.ts @@ -0,0 +1,65 @@ +import { formatCli } from '../../src/bin-format/format-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** "repository" contains a github URL which can be shortened further */ +describe('Github shorthand', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + omitName: true, + otherProps: { + repository: { + url: 'git://github.com/User/repo', + type: 'git', + }, + }, + }), + after: mockPackage('a', { + omitName: true, + otherProps: { + repository: 'User/repo', + }, + }), + }, + ], + {}, + ); + } + + describe('fix-mismatches', () => { + // + }); + + describe('format', () => { + it('uses github shorthand format for "repository"', () => { + const scenario = getScenario(); + formatCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + ]); + }); + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/github-shorthand.ts b/test/scenarios/github-shorthand.ts deleted file mode 100644 index 36ae9be1..00000000 --- a/test/scenarios/github-shorthand.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** "repository" contains a github URL which can be shortened further */ -export function githubShorthand() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - omitName: true, - otherProps: { - repository: { - url: 'git://github.com/User/repo', - type: 'git', - }, - }, - }), - after: mockPackage('a', { - omitName: true, - otherProps: { - repository: 'User/repo', - }, - }), - }, - ], - {}, - ); -} diff --git a/test/scenarios/ignored-semver-ranges-do-not-match-config.spec.ts b/test/scenarios/ignored-semver-ranges-do-not-match-config.spec.ts new file mode 100644 index 00000000..2756472b --- /dev/null +++ b/test/scenarios/ignored-semver-ranges-do-not-match-config.spec.ts @@ -0,0 +1,100 @@ +import { normalize } from 'path'; +import { lintSemverRangesCli } from '../../src/bin-lint-semver-ranges/lint-semver-ranges-cli'; +import { setSemverRangesCli } from '../../src/bin-set-semver-ranges/set-semver-ranges-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - All dependencies are checked + * - The semver range `~` should be used + * - `one` uses exact versions + * - `b` and `c` are ignored + * - All but `b` and `c` should use `~` + */ +describe('Ignored semver ranges do not match config', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + deps: ['a@0.1.0'], + devDeps: ['b@0.1.0'], + overrides: ['c@0.1.0'], + pnpmOverrides: ['d@0.1.0'], + peerDeps: ['e@0.1.0'], + resolutions: ['f@0.1.0'], + }), + after: mockPackage('a', { + deps: ['a@~0.1.0'], + devDeps: ['b@0.1.0'], + overrides: ['c@0.1.0'], + pnpmOverrides: ['d@~0.1.0'], + peerDeps: ['e@~0.1.0'], + resolutions: ['f@~0.1.0'], + }), + }, + ], + { + semverRange: '~', + semverGroups: [ + { + dependencies: ['b', 'c'], + dependencyTypes: [], + packages: ['**'], + isIgnored: true, + }, + ], + types: 'dev,overrides,pnpmOverrides,peer,prod,resolutions,workspace', + }, + ); + } + + describe('fix-mismatches', () => { + // + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + it('list issues from multiple custom types and semver groups together', () => { + const scenario = getScenario(); + const a = normalize('packages/a/package.json'); + lintSemverRangesCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + [expect.stringMatching('Default Semver Group')], + ['✘ a'], + [` 0.1.0 → ~0.1.0 in dependencies of ${a}`], + ['✘ d'], + [` 0.1.0 → ~0.1.0 in pnpm.overrides of ${a}`], + ['✘ e'], + [` 0.1.0 → ~0.1.0 in peerDependencies of ${a}`], + ['✘ f'], + [` 0.1.0 → ~0.1.0 in resolutions of ${a}`], + ]); + }); + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + it('fixes multiple custom types and semver groups together', () => { + const scenario = getScenario(); + setSemverRangesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + ]); + }); + }); +}); diff --git a/test/scenarios/ignored-semver-ranges-do-not-match-config.ts b/test/scenarios/ignored-semver-ranges-do-not-match-config.ts deleted file mode 100644 index 63e8f705..00000000 --- a/test/scenarios/ignored-semver-ranges-do-not-match-config.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - All dependencies are checked - * - The semver range `~` should be used - * - `one` uses exact versions - * - `b` and `c` are ignored - * - All but `b` and `c` should use `~` - */ -export function ignoredSemverRangesDoNotMatchConfig() { - return createScenario( - [ - { - path: 'packages/one/package.json', - before: mockPackage('one', { - deps: ['a@0.1.0'], - devDeps: ['b@0.1.0'], - overrides: ['c@0.1.0'], - pnpmOverrides: ['d@0.1.0'], - peerDeps: ['e@0.1.0'], - resolutions: ['f@0.1.0'], - }), - after: mockPackage('two', { - deps: ['a@~0.1.0'], - devDeps: ['b@0.1.0'], - overrides: ['c@0.1.0'], - pnpmOverrides: ['d@~0.1.0'], - peerDeps: ['e@~0.1.0'], - resolutions: ['f@~0.1.0'], - }), - }, - ], - { - semverRange: '~', - semverGroups: [ - { - dependencies: ['b', 'c'], - dependencyTypes: [], - packages: ['**'], - isIgnored: true, - }, - ], - types: 'dev,overrides,pnpmOverrides,peer,prod,resolutions,workspace', - }, - ); -} diff --git a/test/scenarios/index.ts b/test/scenarios/index.ts deleted file mode 100644 index bd747a7c..00000000 --- a/test/scenarios/index.ts +++ /dev/null @@ -1,642 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './create-scenario'; - -export const scenarios = { - /** "keywords" array should be A-Z but is not */ - sortArrayProps() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - otherProps: { keywords: ['B', 'A'] }, - }), - after: mockPackage('a', { - otherProps: { keywords: ['A', 'B'] }, - }), - }, - ], - { - sortAz: ['keywords'], - }, - ); - }, - - /** "scripts" object keys should be A-Z but is not */ - sortObjectProps() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - otherProps: { scripts: { B: '', A: '' } }, - }), - after: mockPackage('a', { - otherProps: { scripts: { A: '', B: '' } }, - }), - }, - ], - { - sortAz: ['scripts'], - }, - ); - }, - - /** F E D should appear first, then the rest in A-Z order */ - sortFirst() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - omitName: true, - otherProps: { A: '', F: '', B: '', D: '', E: '' }, - }), - after: mockPackage('a', { - omitName: true, - otherProps: { F: '', E: '', D: '', A: '', B: '' }, - }), - }, - ], - { - sortFirst: ['F', 'E', 'D'], - }, - ); - }, - - /** "bugs" and "repository" can safely use equivalent shorthands */ - shorthand() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - omitName: true, - otherProps: { - bugs: { url: 'https://github.com/User/repo/issues' }, - repository: { url: 'git://gitlab.com/User/repo', type: 'git' }, - }, - }), - after: mockPackage('a', { - omitName: true, - otherProps: { - bugs: 'https://github.com/User/repo/issues', - repository: 'git://gitlab.com/User/repo', - }, - }), - }, - ], - {}, - ); - }, - - /** "repository" contains properties which cannot be shortened */ - protectedShorthand() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - omitName: true, - otherProps: { - repository: { - url: 'git://gitlab.com/User/repo', - type: 'git', - directory: 'packages/foo', - }, - }, - }), - after: mockPackage('a', { - omitName: true, - otherProps: { - repository: { - url: 'git://gitlab.com/User/repo', - type: 'git', - directory: 'packages/foo', - }, - }, - }), - }, - ], - {}, - ); - }, - - /** "repository" contains a github URL which can be shortened further */ - githubShorthand() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - omitName: true, - otherProps: { - repository: { - url: 'git://github.com/User/repo', - type: 'git', - }, - }, - }), - after: mockPackage('a', { - omitName: true, - otherProps: { - repository: 'User/repo', - }, - }), - }, - ], - {}, - ); - }, - - /** - * - A does not depend on `bar` - * - B does depend on `bar` - * - `bar` is banned in every package from being installed - * - `bar` should be removed from B - */ - dependencyIsBanned() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['foo@0.1.0'] }), - after: mockPackage('a', { deps: ['foo@0.1.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['bar@0.2.0'] }), - after: mockPackage('b'), - }, - ], - { - versionGroups: [ - { - dependencies: ['bar'], - dependencyTypes: [], - packages: ['**'], - isBanned: true, - }, - ], - }, - ); - }, - - /** "bar" should always be 2.2.2 but is not */ - dependencyIsPinned() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['bar@0.2.0'] }), - after: mockPackage('a', { deps: ['bar@2.2.2'] }), - }, - ], - { - versionGroups: [ - { - dependencies: ['bar'], - dependencyTypes: [], - packages: ['**'], - pinVersion: '2.2.2', - }, - ], - }, - ); - }, - - /** "bar" should be 0.3.0, which is the highest installed version */ - useHighestVersion() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['bar@0.2.0'] }), - after: mockPackage('a', { deps: ['bar@0.3.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['bar@0.3.0'] }), - after: mockPackage('b', { deps: ['bar@0.3.0'] }), - }, - { - path: 'packages/c/package.json', - before: mockPackage('c', { deps: ['bar@0.1.0'] }), - after: mockPackage('c', { deps: ['bar@0.3.0'] }), - }, - ], - {}, - ); - }, - - /** - * - A does not depend on `bar` - * - B does depend on `bar` - * - `bar` is ignored by syncpack in every package - * - `foo` is not ignored - * - `bar` is unprotected so can have mismatching range etc - * - only `foo` should have its semver range fixed - */ - semverIsIgnored() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['foo@0.1.0', 'bar@1.1.1'] }), - after: mockPackage('a', { deps: ['foo@~0.1.0', 'bar@1.1.1'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['bar@0.2.0'] }), - after: mockPackage('b', { deps: ['bar@0.2.0'] }), - }, - ], - { - semverRange: '~', - semverGroups: [ - { - dependencies: ['bar'], - dependencyTypes: [], - packages: ['**'], - isIgnored: true, - }, - ], - }, - ); - }, - - /** - * - A does not depend on `bar` - * - B does depend on `bar` - * - `bar` is ignored by syncpack in every package - * - `bar` is unprotected so can mismatch etc - */ - versionIsIgnored() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['foo@0.1.0', 'bar@1.1.1'] }), - after: mockPackage('a', { deps: ['foo@0.1.0', 'bar@1.1.1'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['bar@0.2.0'] }), - after: mockPackage('b', { deps: ['bar@0.2.0'] }), - }, - ], - { - versionGroups: [ - { - dependencies: ['bar'], - dependencyTypes: [], - packages: ['**'], - isIgnored: true, - }, - ], - }, - ); - }, - - /** - * - A, B, C & D depend on foo - * - The versions mismatch - * - Some versions are not semver - * - `0.3.0` is the highest valid semver version - * - All packages should be fixed to use `0.3.0` - */ - mismatchesIncludeNonSemverVersions() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['foo@link:vendor/foo-0.1.0'] }), - after: mockPackage('a', { deps: ['foo@0.3.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['foo@workspace:*'] }), - after: mockPackage('b', { deps: ['foo@0.3.0'] }), - }, - { - path: 'packages/c/package.json', - before: mockPackage('c', { deps: ['foo@0.3.0'] }), - after: mockPackage('c', { deps: ['foo@0.3.0'] }), - }, - { - path: 'packages/d/package.json', - before: mockPackage('d', { deps: ['foo@0.2.0'] }), - after: mockPackage('d', { deps: ['foo@0.3.0'] }), - }, - ], - {}, - ); - }, - - /** - * - C is developed in this monorepo, its version is `0.0.1` - * - C's version is the single source of truth and should never be changed - * - A depends on C incorrectly and should be fixed - * - B depends on C incorrectly and should be fixed - */ - dependentDoesNotMatchWorkspaceVersion() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['c@0.1.0'] }), - after: mockPackage('a', { deps: ['c@0.0.1'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { devDeps: ['c@0.2.0'] }), - after: mockPackage('b', { devDeps: ['c@0.0.1'] }), - }, - { - path: 'packages/c/package.json', - before: mockPackage('c', { - otherProps: { name: 'c', version: '0.0.1' }, - }), - after: mockPackage('c', { - otherProps: { name: 'c', version: '0.0.1' }, - }), - }, - ], - {}, - ); - }, - - /** - * Variation of `dependentDoesNotMatchWorkspaceVersion` in a nested workspace. - * - * - C is developed in this monorepo, its version is `0.0.1` - * - C's version is the single source of truth and should never be changed - * - A and B depend on C incorrectly and should be fixed - * - A, B, and C are in nested workspaces - * - * @see https://github.com/goldstack/goldstack/pull/170/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519R19 - * @see https://github.com/JamieMason/syncpack/pull/74 - * @see https://github.com/JamieMason/syncpack/issues/66 - */ - dependentDoesNotMatchNestedWorkspaceVersion() { - return createScenario( - [ - { - path: 'workspaces/a/packages/a/package.json', - before: mockPackage('a', { deps: ['c@0.1.0'] }), - after: mockPackage('a', { deps: ['c@0.0.1'] }), - }, - { - path: 'workspaces/b/packages/b/package.json', - before: mockPackage('b', { devDeps: ['c@0.2.0'] }), - after: mockPackage('b', { devDeps: ['c@0.0.1'] }), - }, - { - path: 'workspaces/b/packages/c/package.json', - before: mockPackage('c', { - otherProps: { name: 'c', version: '0.0.1' }, - }), - after: mockPackage('c', { - otherProps: { name: 'c', version: '0.0.1' }, - }), - }, - ], - { - types: 'dev,prod,workspace', - source: [ - 'package.json', - 'workspaces/*/package.json', - 'workspaces/*/packages/*/package.json', - ], - }, - ); - }, - - /** - * - Only `dependencies` is unchecked - * - The semver range `~` should be used - * - A uses exact versions for `a` - * - A should be fixed to use `~` in all other cases - */ - semverRangesDoNotMatchConfig() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - deps: ['a@0.1.0'], - devDeps: ['b@0.1.0'], - overrides: ['c@0.1.0'], - pnpmOverrides: ['d@0.1.0'], - peerDeps: ['e@0.1.0'], - resolutions: ['f@0.1.0'], - }), - after: mockPackage('a', { - deps: ['a@0.1.0'], - devDeps: ['b@~0.1.0'], - overrides: ['c@~0.1.0'], - pnpmOverrides: ['d@~0.1.0'], - peerDeps: ['e@~0.1.0'], - resolutions: ['f@~0.1.0'], - }), - }, - ], - { - semverRange: '~', - types: 'dev,overrides,pnpmOverrides,peer,resolutions,workspace', - }, - ); - }, - - /** - * - All dependencies are checked - * - The semver range `~` should be used - * - `one` uses exact versions - * - `b` and `c` are ignored - * - All but `b` and `c` should use `~` - */ - ignoredSemverRangesDoNotMatchConfig() { - return createScenario( - [ - { - path: 'packages/one/package.json', - before: mockPackage('one', { - deps: ['a@0.1.0'], - devDeps: ['b@0.1.0'], - overrides: ['c@0.1.0'], - pnpmOverrides: ['d@0.1.0'], - peerDeps: ['e@0.1.0'], - resolutions: ['f@0.1.0'], - }), - after: mockPackage('two', { - deps: ['a@~0.1.0'], - devDeps: ['b@0.1.0'], - overrides: ['c@0.1.0'], - pnpmOverrides: ['d@~0.1.0'], - peerDeps: ['e@~0.1.0'], - resolutions: ['f@~0.1.0'], - }), - }, - ], - { - semverRange: '~', - semverGroups: [ - { - dependencies: ['b', 'c'], - dependencyTypes: [], - packages: ['**'], - isIgnored: true, - }, - ], - types: 'dev,overrides,pnpmOverrides,peer,prod,resolutions,workspace', - }, - ); - }, - - /** - * - Only `dependencies` is unchecked - * - The semver range `*` should be used - * - A uses exact versions for `a` - * - A should be fixed to use `*` in all other cases - */ - semverRangesDoNotMatchConfigWildcard() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - deps: ['a@0.1.0'], - devDeps: ['b@0.1.0'], - overrides: ['c@0.1.0'], - pnpmOverrides: ['d@0.1.0'], - peerDeps: ['e@0.1.0'], - resolutions: ['f@0.1.0'], - }), - after: mockPackage('a', { - deps: ['a@0.1.0'], - devDeps: ['*'], - overrides: ['*'], - pnpmOverrides: ['*'], - peerDeps: ['*'], - resolutions: ['*'], - }), - }, - ], - { - semverRange: '*', - types: 'dev,overrides,pnpmOverrides,peer,resolutions,workspace', - }, - ); - }, - - /** - * - A has a pnpm override of C - * - B has a pnpm override of C - * - The versions do not match - * - The highest semver version wins - */ - dependentDoesNotMatchPnpmOverrideVersion() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { pnpmOverrides: ['c@0.1.0'] }), - after: mockPackage('a', { pnpmOverrides: ['c@0.2.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { pnpmOverrides: ['c@0.2.0'] }), - after: mockPackage('b', { pnpmOverrides: ['c@0.2.0'] }), - }, - ], - { - types: 'pnpmOverrides', - }, - ); - }, - - /** - * - A depends on C - * - B depends on C - * - The versions do not match - * - A filter is set to only look at dependency D - * - The mismatches should be ignored - */ - mismatchIsFilteredOut() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['c@0.1.0'] }), - after: mockPackage('a', { deps: ['c@0.1.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['c@0.2.0', 'd@1.1.1'] }), - after: mockPackage('b', { deps: ['c@0.2.0', 'd@1.1.1'] }), - }, - ], - { - filter: 'd', - }, - ); - }, - - /** - * - A has an npm override of C - * - B has an npm override of C - * - The versions do not match - * - The highest semver version wins - */ - dependentDoesNotMatchNpmOverrideVersion() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { pnpmOverrides: ['c@0.1.0'] }), - after: mockPackage('a', { pnpmOverrides: ['c@0.2.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { pnpmOverrides: ['c@0.2.0'] }), - after: mockPackage('b', { pnpmOverrides: ['c@0.2.0'] }), - }, - ], - { - types: 'pnpmOverrides', - }, - ); - }, - - /** - * @see https://github.com/JamieMason/syncpack/issues/84#issue-1284878219 - */ - issue84Reproduction() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('@myscope/a', { deps: ['@myscope/a@1.0.0'] }), - after: mockPackage('@myscope/a', { deps: ['@myscope/a@^1.0.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('@myscope/b', {}), - after: mockPackage('@myscope/b', {}), - }, - ], - { - semverGroups: [ - { - range: '^', - dependencies: ['@myscope/**'], - dependencyTypes: [], - packages: ['**'], - }, - ], - semverRange: '~', - types: 'dev,overrides,pnpmOverrides,peer,prod,resolutions,workspace', - }, - ); - }, -}; diff --git a/test/scenarios/issue84-reproduction.spec.ts b/test/scenarios/issue84-reproduction.spec.ts new file mode 100644 index 00000000..7d20029d --- /dev/null +++ b/test/scenarios/issue84-reproduction.spec.ts @@ -0,0 +1,71 @@ +import { setSemverRangesCli } from '../../src/bin-set-semver-ranges/set-semver-ranges-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * @see https://github.com/JamieMason/syncpack/issues/84#issue-1284878219 + */ +describe('Issue 84 reproduction', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('@myscope/a', { deps: ['@myscope/a@1.0.0'] }), + after: mockPackage('@myscope/a', { deps: ['@myscope/a@^1.0.0'] }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('@myscope/b', {}), + after: mockPackage('@myscope/b', {}), + }, + ], + { + semverGroups: [ + { + range: '^', + dependencies: ['@myscope/**'], + dependencyTypes: [], + packages: ['**'], + }, + ], + semverRange: '~', + types: 'dev,overrides,pnpmOverrides,peer,prod,resolutions,workspace', + }, + ); + } + + describe('fix-mismatches', () => { + // + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + it('fixes issue 84', () => { + const scenario = getScenario(); + setSemverRangesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + ]); + }); + }); +}); diff --git a/test/scenarios/issue84-reproduction.ts b/test/scenarios/issue84-reproduction.ts deleted file mode 100644 index 960a42d7..00000000 --- a/test/scenarios/issue84-reproduction.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * @see https://github.com/JamieMason/syncpack/issues/84#issue-1284878219 - */ -export function issue84Reproduction() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('@myscope/a', { deps: ['@myscope/a@1.0.0'] }), - after: mockPackage('@myscope/a', { deps: ['@myscope/a@^1.0.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('@myscope/b', {}), - after: mockPackage('@myscope/b', {}), - }, - ], - { - semverGroups: [ - { - range: '^', - dependencies: ['@myscope/**'], - dependencyTypes: [], - packages: ['**'], - }, - ], - semverRange: '~', - types: 'dev,overrides,pnpmOverrides,peer,prod,resolutions,workspace', - }, - ); -} diff --git a/test/scenarios/matching-unsupported-versions.spec.ts b/test/scenarios/matching-unsupported-versions.spec.ts new file mode 100644 index 00000000..a78a3d46 --- /dev/null +++ b/test/scenarios/matching-unsupported-versions.spec.ts @@ -0,0 +1,81 @@ +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { listMismatchesCli } from '../../src/bin-list-mismatches/list-mismatches-cli'; +import { listCli } from '../../src/bin-list/list-cli'; +import { setSemverRangesCli } from '../../src/bin-set-semver-ranges/set-semver-ranges-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - A and B have versions syncpack does not support + * - The versions match + * - All packages should be left unchanged + */ +describe('Matching unsupported versions', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { deps: ['foo@workspace:*'] }), + after: mockPackage('a', { deps: ['foo@workspace:*'] }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { deps: ['foo@workspace:*'] }), + after: mockPackage('b', { deps: ['foo@workspace:*'] }), + }, + ], + {}, + ); + } + + describe('fix-mismatches', () => { + it('skips matching versions which syncpack cannot fix anyway', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync).not.toHaveBeenCalled(); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenUnchanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + it('does not list matching versions which syncpack cannot fix anyway', () => { + const scenario = getScenario(); + listMismatchesCli(scenario.config, scenario.disk); + expect(scenario.log).not.toHaveBeenCalled(); + expect(scenario.disk.process.exit).not.toHaveBeenCalled(); + }); + }); + + describe('list', () => { + it('lists matching versions which syncpack cannot fix anyway', () => { + const scenario = getScenario(); + listCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([['- foo workspace:*']]); + expect(scenario.disk.process.exit).not.toHaveBeenCalled(); + }); + }); + + describe('set-semver-ranges', () => { + it('leaves non-semver versions unchanged', () => { + const scenario = getScenario(); + setSemverRangesCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenUnchanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + ]); + expect(scenario.disk.process.exit).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/test/scenarios/matching-unsupported-versions.ts b/test/scenarios/matching-unsupported-versions.ts deleted file mode 100644 index 7212560c..00000000 --- a/test/scenarios/matching-unsupported-versions.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - A and B have versions syncpack does not support - * - The versions match - * - All packages should be left unchanged - */ -export function matchingUnsupportedVersions() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['foo@workspace:*'] }), - after: mockPackage('a', { deps: ['foo@workspace:*'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['foo@workspace:*'] }), - after: mockPackage('b', { deps: ['foo@workspace:*'] }), - }, - ], - {}, - ); -} diff --git a/test/scenarios/mismatch-is-filtered-out.spec.ts b/test/scenarios/mismatch-is-filtered-out.spec.ts new file mode 100644 index 00000000..c4c548b4 --- /dev/null +++ b/test/scenarios/mismatch-is-filtered-out.spec.ts @@ -0,0 +1,61 @@ +import { listCli } from '../../src/bin-list/list-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - A depends on C + * - B depends on C + * - The versions do not match + * - A filter is set to only look at dependency D + * - The mismatches should be ignored + */ +describe('Mismatch is filtered out', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { deps: ['c@0.1.0'] }), + after: mockPackage('a', { deps: ['c@0.1.0'] }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { deps: ['c@0.2.0', 'd@1.1.1'] }), + after: mockPackage('b', { deps: ['c@0.2.0', 'd@1.1.1'] }), + }, + ], + { + filter: 'd', + }, + ); + } + + describe('fix-mismatches', () => { + // + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + it('ignores mismatches which do not match applied filter', () => { + const scenario = getScenario(); + listCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([['- d 1.1.1']]); + expect(scenario.disk.process.exit).not.toHaveBeenCalled(); + }); + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/mismatch-is-filtered-out.ts b/test/scenarios/mismatch-is-filtered-out.ts deleted file mode 100644 index d3d5257c..00000000 --- a/test/scenarios/mismatch-is-filtered-out.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - A depends on C - * - B depends on C - * - The versions do not match - * - A filter is set to only look at dependency D - * - The mismatches should be ignored - */ -export function mismatchIsFilteredOut() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['c@0.1.0'] }), - after: mockPackage('a', { deps: ['c@0.1.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['c@0.2.0', 'd@1.1.1'] }), - after: mockPackage('b', { deps: ['c@0.2.0', 'd@1.1.1'] }), - }, - ], - { - filter: 'd', - }, - ); -} diff --git a/test/scenarios/mismatching-unsupported-versions.spec.ts b/test/scenarios/mismatching-unsupported-versions.spec.ts new file mode 100644 index 00000000..9568aa09 --- /dev/null +++ b/test/scenarios/mismatching-unsupported-versions.spec.ts @@ -0,0 +1,103 @@ +import { normalize } from 'path'; +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { listMismatchesCli } from '../../src/bin-list-mismatches/list-mismatches-cli'; +import { listCli } from '../../src/bin-list/list-cli'; +import { ICON } from '../../src/constants'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - A, B, C & D depend on foo + * - The versions mismatch + * - Some versions are not semver + * - `0.3.0` is the highest valid semver version + * - Syncpack can't know what the Developers intend with them + * - All packages should be left unchanged + */ +describe('Mismatching unsupported versions', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { deps: ['foo@link:vendor/foo-0.1.0'] }), + after: mockPackage('a', { deps: ['foo@link:vendor/foo-0.1.0'] }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { deps: ['foo@workspace:*'] }), + after: mockPackage('b', { deps: ['foo@workspace:*'] }), + }, + { + path: 'packages/c/package.json', + before: mockPackage('c', { deps: ['foo@0.3.0'] }), + after: mockPackage('c', { deps: ['foo@0.3.0'] }), + }, + { + path: 'packages/d/package.json', + before: mockPackage('d', { deps: ['foo@0.2.0'] }), + after: mockPackage('d', { deps: ['foo@0.2.0'] }), + }, + ], + {}, + ); + } + + describe('fix-mismatches', () => { + it('skips mismatched versions which syncpack cannot fix', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync).not.toHaveBeenCalled(); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenUnchanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + scenario.files['packages/c/package.json'].logEntryWhenUnchanged, + scenario.files['packages/d/package.json'].logEntryWhenUnchanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + it('lists mismatched versions which syncpack cannot fix', () => { + const scenario = getScenario(); + const a = 'packages/a/package.json'; + const b = 'packages/b/package.json'; + const c = 'packages/c/package.json'; + const d = 'packages/d/package.json'; + listMismatchesCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + [`${ICON.cross} foo has mismatched versions which syncpack cannot fix`], + [` link:vendor/foo-0.1.0 in dependencies of ${normalize(a)}`], + [` workspace:* in dependencies of ${normalize(b)}`], + [` 0.3.0 in dependencies of ${normalize(c)}`], + [` 0.2.0 in dependencies of ${normalize(d)}`], + ]); + expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); + }); + }); + + describe('list', () => { + it('lists mismatched versions which syncpack cannot fix', () => { + const scenario = getScenario(); + listCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + [ + '✘ foo has mismatched versions which syncpack cannot fix: 0.2.0, 0.3.0, link:vendor/foo-0.1.0, workspace:*', + ], + ]); + expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); + }); + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/mismatching-unsupported-versions.ts b/test/scenarios/mismatching-unsupported-versions.ts deleted file mode 100644 index 2c7cf526..00000000 --- a/test/scenarios/mismatching-unsupported-versions.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - A, B, C & D depend on foo - * - The versions mismatch - * - Some versions are not semver - * - `0.3.0` is the highest valid semver version - * - Syncpack can't know what the Developers intend with them - * - All packages should be left unchanged - */ -export function mismatchingUnsupportedVersions() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['foo@link:vendor/foo-0.1.0'] }), - after: mockPackage('a', { deps: ['foo@link:vendor/foo-0.1.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['foo@workspace:*'] }), - after: mockPackage('b', { deps: ['foo@workspace:*'] }), - }, - { - path: 'packages/c/package.json', - before: mockPackage('c', { deps: ['foo@0.3.0'] }), - after: mockPackage('c', { deps: ['foo@0.3.0'] }), - }, - { - path: 'packages/d/package.json', - before: mockPackage('d', { deps: ['foo@0.2.0'] }), - after: mockPackage('d', { deps: ['foo@0.2.0'] }), - }, - ], - {}, - ); -} diff --git a/test/scenarios/protected-shorthand.spec.ts b/test/scenarios/protected-shorthand.spec.ts new file mode 100644 index 00000000..897e997e --- /dev/null +++ b/test/scenarios/protected-shorthand.spec.ts @@ -0,0 +1,68 @@ +import { formatCli } from '../../src/bin-format/format-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** "repository" contains properties which cannot be shortened */ +describe('Protected shorthand', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + omitName: true, + otherProps: { + repository: { + url: 'git://gitlab.com/User/repo', + type: 'git', + directory: 'packages/foo', + }, + }, + }), + after: mockPackage('a', { + omitName: true, + otherProps: { + repository: { + url: 'git://gitlab.com/User/repo', + type: 'git', + directory: 'packages/foo', + }, + }, + }), + }, + ], + {}, + ); + } + + describe('fix-mismatches', () => { + // + }); + + describe('format', () => { + it('retains long form format for "repository" when directory property used', () => { + const scenario = getScenario(); + formatCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenUnchanged, + ]); + }); + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/protected-shorthand.ts b/test/scenarios/protected-shorthand.ts deleted file mode 100644 index a5477191..00000000 --- a/test/scenarios/protected-shorthand.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** "repository" contains properties which cannot be shortened */ -export function protectedShorthand() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - omitName: true, - otherProps: { - repository: { - url: 'git://gitlab.com/User/repo', - type: 'git', - directory: 'packages/foo', - }, - }, - }), - after: mockPackage('a', { - omitName: true, - otherProps: { - repository: { - url: 'git://gitlab.com/User/repo', - type: 'git', - directory: 'packages/foo', - }, - }, - }), - }, - ], - {}, - ); -} diff --git a/test/scenarios/semver-is-ignored.spec.ts b/test/scenarios/semver-is-ignored.spec.ts new file mode 100644 index 00000000..94453049 --- /dev/null +++ b/test/scenarios/semver-is-ignored.spec.ts @@ -0,0 +1,86 @@ +import { normalize } from 'path'; +import { lintSemverRangesCli } from '../../src/bin-lint-semver-ranges/lint-semver-ranges-cli'; +import { setSemverRangesCli } from '../../src/bin-set-semver-ranges/set-semver-ranges-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - A does not depend on `bar` + * - B does depend on `bar` + * - `bar` is ignored by syncpack in every package + * - `foo` is not ignored + * - `bar` is unprotected so can have mismatching range etc + * - only `foo` should have its semver range fixed + */ +describe('Semver is ignored', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { deps: ['foo@0.1.0', 'bar@1.1.1'] }), + after: mockPackage('a', { deps: ['foo@~0.1.0', 'bar@1.1.1'] }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { deps: ['bar@0.2.0'] }), + after: mockPackage('b', { deps: ['bar@0.2.0'] }), + }, + ], + { + semverRange: '~', + semverGroups: [ + { + dependencies: ['bar'], + dependencyTypes: [], + packages: ['**'], + isIgnored: true, + }, + ], + }, + ); + } + + describe('fix-mismatches', () => { + // + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + it('does not include ignored dependencies in its output', () => { + const scenario = getScenario(); + const a = normalize('packages/a/package.json'); + lintSemverRangesCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + [expect.stringMatching(/Default Semver Group/)], + ['✘ foo'], + [` 0.1.0 → ~0.1.0 in dependencies of ${a}`], + ]); + }); + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + it('leaves ignored dependencies unchanged', () => { + const scenario = getScenario(); + setSemverRangesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + ]); + }); + }); +}); diff --git a/test/scenarios/semver-is-ignored.ts b/test/scenarios/semver-is-ignored.ts deleted file mode 100644 index 52eccb53..00000000 --- a/test/scenarios/semver-is-ignored.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - A does not depend on `bar` - * - B does depend on `bar` - * - `bar` is ignored by syncpack in every package - * - `foo` is not ignored - * - `bar` is unprotected so can have mismatching range etc - * - only `foo` should have its semver range fixed - */ -export function semverIsIgnored() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['foo@0.1.0', 'bar@1.1.1'] }), - after: mockPackage('a', { deps: ['foo@~0.1.0', 'bar@1.1.1'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['bar@0.2.0'] }), - after: mockPackage('b', { deps: ['bar@0.2.0'] }), - }, - ], - { - semverRange: '~', - semverGroups: [ - { - dependencies: ['bar'], - dependencyTypes: [], - packages: ['**'], - isIgnored: true, - }, - ], - }, - ); -} diff --git a/test/scenarios/semver-ranges-do-not-match-config-wildcard.spec.ts b/test/scenarios/semver-ranges-do-not-match-config-wildcard.spec.ts new file mode 100644 index 00000000..8cd4500e --- /dev/null +++ b/test/scenarios/semver-ranges-do-not-match-config-wildcard.spec.ts @@ -0,0 +1,82 @@ +import { normalize } from 'path'; +import { lintSemverRangesCli } from '../../src/bin-lint-semver-ranges/lint-semver-ranges-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - Only `dependencies` is unchecked + * - The semver range `*` should be used + * - A uses exact versions for `a` + * - A should be fixed to use `*` in all other cases + */ +describe('Semver ranges do not match config wildcard', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + deps: ['a@0.1.0'], + devDeps: ['b@0.1.0'], + overrides: ['c@0.1.0'], + pnpmOverrides: ['d@0.1.0'], + peerDeps: ['e@0.1.0'], + resolutions: ['f@0.1.0'], + }), + after: mockPackage('a', { + deps: ['a@0.1.0'], + devDeps: ['*'], + overrides: ['*'], + pnpmOverrides: ['*'], + peerDeps: ['*'], + resolutions: ['*'], + }), + }, + ], + { + semverRange: '*', + types: 'dev,overrides,pnpmOverrides,peer,resolutions,workspace', + }, + ); + } + + describe('fix-mismatches', () => { + // + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + it('ensures wildcard versions are supported', () => { + const scenario = getScenario(); + const a = normalize('packages/a/package.json'); + lintSemverRangesCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + ['✘ b'], + [` 0.1.0 → * in devDependencies of ${a}`], + ['✘ c'], + [` 0.1.0 → * in overrides of ${a}`], + ['✘ d'], + [` 0.1.0 → * in pnpm.overrides of ${a}`], + ['✘ e'], + [` 0.1.0 → * in peerDependencies of ${a}`], + ['✘ f'], + [` 0.1.0 → * in resolutions of ${a}`], + ]); + }); + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/semver-ranges-do-not-match-config-wildcard.ts b/test/scenarios/semver-ranges-do-not-match-config-wildcard.ts deleted file mode 100644 index fd18d887..00000000 --- a/test/scenarios/semver-ranges-do-not-match-config-wildcard.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - Only `dependencies` is unchecked - * - The semver range `*` should be used - * - A uses exact versions for `a` - * - A should be fixed to use `*` in all other cases - */ -export function semverRangesDoNotMatchConfigWildcard() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - deps: ['a@0.1.0'], - devDeps: ['b@0.1.0'], - overrides: ['c@0.1.0'], - pnpmOverrides: ['d@0.1.0'], - peerDeps: ['e@0.1.0'], - resolutions: ['f@0.1.0'], - }), - after: mockPackage('a', { - deps: ['a@0.1.0'], - devDeps: ['*'], - overrides: ['*'], - pnpmOverrides: ['*'], - peerDeps: ['*'], - resolutions: ['*'], - }), - }, - ], - { - semverRange: '*', - types: 'dev,overrides,pnpmOverrides,peer,resolutions,workspace', - }, - ); -} diff --git a/test/scenarios/semver-ranges-do-not-match-config.spec.ts b/test/scenarios/semver-ranges-do-not-match-config.spec.ts new file mode 100644 index 00000000..e1adee3a --- /dev/null +++ b/test/scenarios/semver-ranges-do-not-match-config.spec.ts @@ -0,0 +1,92 @@ +import { normalize } from 'path'; +import { lintSemverRangesCli } from '../../src/bin-lint-semver-ranges/lint-semver-ranges-cli'; +import { setSemverRangesCli } from '../../src/bin-set-semver-ranges/set-semver-ranges-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - Only `dependencies` is unchecked + * - The semver range `~` should be used + * - A uses exact versions for `a` + * - A should be fixed to use `~` in all other cases + */ +describe('Semver ranges do not match config', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + deps: ['a@0.1.0'], + devDeps: ['b@0.1.0'], + overrides: ['c@0.1.0'], + pnpmOverrides: ['d@0.1.0'], + peerDeps: ['e@0.1.0'], + resolutions: ['f@0.1.0'], + }), + after: mockPackage('a', { + deps: ['a@0.1.0'], + devDeps: ['b@~0.1.0'], + overrides: ['c@~0.1.0'], + pnpmOverrides: ['d@~0.1.0'], + peerDeps: ['e@~0.1.0'], + resolutions: ['f@~0.1.0'], + }), + }, + ], + { + semverRange: '~', + types: 'dev,overrides,pnpmOverrides,peer,resolutions,workspace', + }, + ); + } + + describe('fix-mismatches', () => { + // + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + it('lists versions with ranges which do not match the project config', () => { + const scenario = getScenario(); + const a = normalize('packages/a/package.json'); + lintSemverRangesCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + ['✘ b'], + [` 0.1.0 → ~0.1.0 in devDependencies of ${a}`], + ['✘ c'], + [` 0.1.0 → ~0.1.0 in overrides of ${a}`], + ['✘ d'], + [` 0.1.0 → ~0.1.0 in pnpm.overrides of ${a}`], + ['✘ e'], + [` 0.1.0 → ~0.1.0 in peerDependencies of ${a}`], + ['✘ f'], + [` 0.1.0 → ~0.1.0 in resolutions of ${a}`], + ]); + }); + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + it('sets all versions to use the supplied range', () => { + const scenario = getScenario(); + setSemverRangesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + ]); + }); + }); +}); diff --git a/test/scenarios/semver-ranges-do-not-match-config.ts b/test/scenarios/semver-ranges-do-not-match-config.ts deleted file mode 100644 index 8f5706b5..00000000 --- a/test/scenarios/semver-ranges-do-not-match-config.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - Only `dependencies` is unchecked - * - The semver range `~` should be used - * - A uses exact versions for `a` - * - A should be fixed to use `~` in all other cases - */ -export function semverRangesDoNotMatchConfig() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - deps: ['a@0.1.0'], - devDeps: ['b@0.1.0'], - overrides: ['c@0.1.0'], - pnpmOverrides: ['d@0.1.0'], - peerDeps: ['e@0.1.0'], - resolutions: ['f@0.1.0'], - }), - after: mockPackage('a', { - deps: ['a@0.1.0'], - devDeps: ['b@~0.1.0'], - overrides: ['c@~0.1.0'], - pnpmOverrides: ['d@~0.1.0'], - peerDeps: ['e@~0.1.0'], - resolutions: ['f@~0.1.0'], - }), - }, - ], - { - semverRange: '~', - types: 'dev,overrides,pnpmOverrides,peer,resolutions,workspace', - }, - ); -} diff --git a/test/scenarios/shorthand-properties.spec.ts b/test/scenarios/shorthand-properties.spec.ts new file mode 100644 index 00000000..afd403f0 --- /dev/null +++ b/test/scenarios/shorthand-properties.spec.ts @@ -0,0 +1,64 @@ +import { formatCli } from '../../src/bin-format/format-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** "bugs" and "repository" can safely use equivalent shorthands */ +describe('shorthand properties', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + omitName: true, + otherProps: { + bugs: { url: 'https://github.com/User/repo/issues' }, + repository: { url: 'git://gitlab.com/User/repo', type: 'git' }, + }, + }), + after: mockPackage('a', { + omitName: true, + otherProps: { + bugs: 'https://github.com/User/repo/issues', + repository: 'git://gitlab.com/User/repo', + }, + }), + }, + ], + {}, + ); + } + + describe('fix-mismatches', () => { + // + }); + + describe('format', () => { + it('uses shorthand format for "bugs" and "repository"', () => { + const scenario = getScenario(); + formatCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + ]); + }); + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/shorthand.ts b/test/scenarios/shorthand.ts deleted file mode 100644 index bb30fd45..00000000 --- a/test/scenarios/shorthand.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** "bugs" and "repository" can safely use equivalent shorthands */ -export function shorthand() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - omitName: true, - otherProps: { - bugs: { url: 'https://github.com/User/repo/issues' }, - repository: { url: 'git://gitlab.com/User/repo', type: 'git' }, - }, - }), - after: mockPackage('a', { - omitName: true, - otherProps: { - bugs: 'https://github.com/User/repo/issues', - repository: 'git://gitlab.com/User/repo', - }, - }), - }, - ], - {}, - ); -} diff --git a/test/scenarios/sort-array-props.spec.ts b/test/scenarios/sort-array-props.spec.ts new file mode 100644 index 00000000..ef35610c --- /dev/null +++ b/test/scenarios/sort-array-props.spec.ts @@ -0,0 +1,58 @@ +import { formatCli } from '../../src/bin-format/format-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** "keywords" array should be A-Z but is not */ +describe('Sort array props', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + otherProps: { keywords: ['B', 'A'] }, + }), + after: mockPackage('a', { + otherProps: { keywords: ['A', 'B'] }, + }), + }, + ], + { + sortAz: ['keywords'], + }, + ); + } + + describe('fix-mismatches', () => { + // + }); + + describe('format', () => { + it('sorts array properties alphabetically by value', () => { + const scenario = getScenario(); + formatCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + ]); + }); + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/sort-array-props.ts b/test/scenarios/sort-array-props.ts deleted file mode 100644 index 96c369b2..00000000 --- a/test/scenarios/sort-array-props.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** "keywords" array should be A-Z but is not */ -export function sortArrayProps() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - otherProps: { keywords: ['B', 'A'] }, - }), - after: mockPackage('a', { - otherProps: { keywords: ['A', 'B'] }, - }), - }, - ], - { - sortAz: ['keywords'], - }, - ); -} diff --git a/test/scenarios/sort-first.spec.ts b/test/scenarios/sort-first.spec.ts new file mode 100644 index 00000000..a9e25cfb --- /dev/null +++ b/test/scenarios/sort-first.spec.ts @@ -0,0 +1,60 @@ +import { formatCli } from '../../src/bin-format/format-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** F E D should appear first, then the rest in A-Z order */ +describe('Sort first', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + omitName: true, + otherProps: { A: '', F: '', B: '', D: '', E: '' }, + }), + after: mockPackage('a', { + omitName: true, + otherProps: { F: '', E: '', D: '', A: '', B: '' }, + }), + }, + ], + { + sortFirst: ['F', 'E', 'D'], + }, + ); + } + + describe('fix-mismatches', () => { + // + }); + + describe('format', () => { + it('sorts named properties first, then the rest alphabetically', () => { + const scenario = getScenario(); + formatCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + ]); + }); + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/sort-first.ts b/test/scenarios/sort-first.ts deleted file mode 100644 index 86e57172..00000000 --- a/test/scenarios/sort-first.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** F E D should appear first, then the rest in A-Z order */ -export function sortFirst() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - omitName: true, - otherProps: { A: '', F: '', B: '', D: '', E: '' }, - }), - after: mockPackage('a', { - omitName: true, - otherProps: { F: '', E: '', D: '', A: '', B: '' }, - }), - }, - ], - { - sortFirst: ['F', 'E', 'D'], - }, - ); -} diff --git a/test/scenarios/sort-object-props.spec.ts b/test/scenarios/sort-object-props.spec.ts new file mode 100644 index 00000000..b442f534 --- /dev/null +++ b/test/scenarios/sort-object-props.spec.ts @@ -0,0 +1,58 @@ +import { formatCli } from '../../src/bin-format/format-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** "scripts" object keys should be A-Z but is not */ +describe('Sort object props', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + otherProps: { scripts: { B: '', A: '' } }, + }), + after: mockPackage('a', { + otherProps: { scripts: { A: '', B: '' } }, + }), + }, + ], + { + sortAz: ['scripts'], + }, + ); + } + + describe('fix-mismatches', () => { + // + }); + + describe('format', () => { + it('sorts object properties alphabetically by key', () => { + const scenario = getScenario(); + formatCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + ]); + }); + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/sort-object-props.ts b/test/scenarios/sort-object-props.ts deleted file mode 100644 index 15e69ac1..00000000 --- a/test/scenarios/sort-object-props.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** "scripts" object keys should be A-Z but is not */ -export function sortObjectProps() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - otherProps: { scripts: { B: '', A: '' } }, - }), - after: mockPackage('a', { - otherProps: { scripts: { A: '', B: '' } }, - }), - }, - ], - { - sortAz: ['scripts'], - }, - ); -} diff --git a/test/scenarios/unused-custom-type.spec.ts b/test/scenarios/unused-custom-type.spec.ts new file mode 100644 index 00000000..96824fd3 --- /dev/null +++ b/test/scenarios/unused-custom-type.spec.ts @@ -0,0 +1,79 @@ +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - .syncpackrc has a custom type defined to check the "engines" property. + * - A has node 14 + * - B has node 16 + * - A should normally be fixed to use 16, but .syncpackrc has filtered + * "dependencyTypes" to only check the "dependencies" property + * - Mismatch should be ignored + */ +describe('Unused custom type', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { + otherProps: { engines: { node: '14.0.0' } }, + }), + after: mockPackage('a', { + otherProps: { engines: { node: '14.0.0' } }, + }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { + otherProps: { engines: { node: '16.0.0' } }, + }), + after: mockPackage('b', { + otherProps: { engines: { node: '14.0.0' } }, + }), + }, + ], + { + types: 'prod', + customTypes: { + engines: { + strategy: 'versionsByName', + path: 'engines', + }, + }, + }, + ); + } + + describe('fix-mismatches', () => { + it('ignores the mismatch in the custom location if it has been filtered out', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync).not.toHaveBeenCalled(); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenUnchanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + // + }); + + describe('list', () => { + // + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/unused-custom-type.ts b/test/scenarios/unused-custom-type.ts deleted file mode 100644 index e0810a02..00000000 --- a/test/scenarios/unused-custom-type.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - .syncpackrc has a custom type defined to check the "engines" property. - * - A has node 14 - * - B has node 16 - * - A should normally be fixed to use 16, but .syncpackrc has filtered - * "dependencyTypes" to only check the "dependencies" property - * - Mismatch should be ignored - */ -export function unusedCustomType() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { - otherProps: { engines: { node: '14.0.0' } }, - }), - after: mockPackage('a', { - otherProps: { engines: { node: '14.0.0' } }, - }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { - otherProps: { engines: { node: '16.0.0' } }, - }), - after: mockPackage('b', { - otherProps: { engines: { node: '14.0.0' } }, - }), - }, - ], - { - types: 'prod', - customTypes: { - engines: { - strategy: 'versionsByName', - path: 'engines', - }, - }, - }, - ); -} diff --git a/test/scenarios/use-highest-version.spec.ts b/test/scenarios/use-highest-version.spec.ts new file mode 100644 index 00000000..9db0e450 --- /dev/null +++ b/test/scenarios/use-highest-version.spec.ts @@ -0,0 +1,85 @@ +import { normalize } from 'path'; +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { listMismatchesCli } from '../../src/bin-list-mismatches/list-mismatches-cli'; +import { listCli } from '../../src/bin-list/list-cli'; +import { ICON } from '../../src/constants'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** "bar" should be 0.3.0, which is the highest installed version */ +describe('Use highest version', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { deps: ['bar@0.2.0'] }), + after: mockPackage('a', { deps: ['bar@0.3.0'] }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { deps: ['bar@0.3.0'] }), + after: mockPackage('b', { deps: ['bar@0.3.0'] }), + }, + { + path: 'packages/c/package.json', + before: mockPackage('c', { deps: ['bar@0.1.0'] }), + after: mockPackage('c', { deps: ['bar@0.3.0'] }), + }, + ], + {}, + ); + } + + describe('fix-mismatches', () => { + it('uses the highest installed version', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].diskWriteWhenChanged, + scenario.files['packages/c/package.json'].diskWriteWhenChanged, + ]); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenChanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + scenario.files['packages/c/package.json'].logEntryWhenChanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + it('uses the highest installed version', () => { + const scenario = getScenario(); + const a = 'packages/a/package.json'; + const c = 'packages/c/package.json'; + listMismatchesCli(scenario.config, scenario.disk); + + expect(scenario.log.mock.calls).toEqual([ + [`${ICON.cross} bar 0.3.0 is the highest valid semver version in use`], + [` 0.2.0 in dependencies of ${normalize(a)}`], + [` 0.1.0 in dependencies of ${normalize(c)}`], + ]); + }); + }); + + describe('list', () => { + it('uses the highest installed version', () => { + const scenario = getScenario(); + listCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([['✘ bar 0.1.0, 0.2.0, 0.3.0']]); + expect(scenario.disk.process.exit).toHaveBeenCalledWith(1); + }); + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/use-highest-version.ts b/test/scenarios/use-highest-version.ts deleted file mode 100644 index fb58fd06..00000000 --- a/test/scenarios/use-highest-version.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** "bar" should be 0.3.0, which is the highest installed version */ -export function useHighestVersion() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['bar@0.2.0'] }), - after: mockPackage('a', { deps: ['bar@0.3.0'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['bar@0.3.0'] }), - after: mockPackage('b', { deps: ['bar@0.3.0'] }), - }, - { - path: 'packages/c/package.json', - before: mockPackage('c', { deps: ['bar@0.1.0'] }), - after: mockPackage('c', { deps: ['bar@0.3.0'] }), - }, - ], - {}, - ); -} diff --git a/test/scenarios/version-is-ignored.spec.ts b/test/scenarios/version-is-ignored.spec.ts new file mode 100644 index 00000000..6fd87241 --- /dev/null +++ b/test/scenarios/version-is-ignored.spec.ts @@ -0,0 +1,88 @@ +import 'expect-more-jest'; +import { fixMismatchesCli } from '../../src/bin-fix-mismatches/fix-mismatches-cli'; +import { listMismatchesCli } from '../../src/bin-list-mismatches/list-mismatches-cli'; +import { listCli } from '../../src/bin-list/list-cli'; +import { mockPackage } from '../mock'; +import { createScenario } from './lib/create-scenario'; + +/** + * - A does not depend on `bar` + * - B does depend on `bar` + * - `bar` is ignored by syncpack in every package + * - `bar` is unprotected so can mismatch etc + */ +describe('Version is ignored', () => { + function getScenario() { + return createScenario( + [ + { + path: 'packages/a/package.json', + before: mockPackage('a', { deps: ['foo@0.1.0', 'bar@1.1.1'] }), + after: mockPackage('a', { deps: ['foo@0.1.0', 'bar@1.1.1'] }), + }, + { + path: 'packages/b/package.json', + before: mockPackage('b', { deps: ['bar@0.2.0'] }), + after: mockPackage('b', { deps: ['bar@0.2.0'] }), + }, + ], + { + versionGroups: [ + { + dependencies: ['bar'], + dependencyTypes: [], + packages: ['**'], + isIgnored: true, + }, + ], + }, + ); + } + + describe('fix-mismatches', () => { + it('does not consider versions of ignored dependencies', () => { + const scenario = getScenario(); + fixMismatchesCli(scenario.config, scenario.disk); + expect(scenario.disk.writeFileSync).not.toHaveBeenCalled(); + expect(scenario.log.mock.calls).toEqual([ + scenario.files['packages/a/package.json'].logEntryWhenUnchanged, + scenario.files['packages/b/package.json'].logEntryWhenUnchanged, + ]); + }); + }); + + describe('format', () => { + // + }); + + describe('lint-semver-ranges', () => { + // + }); + + describe('list-mismatches', () => { + it('does not mention ignored dependencies', () => { + const scenario = getScenario(); + listMismatchesCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toBeEmptyArray(); + expect(scenario.disk.process.exit).not.toHaveBeenCalled(); + }); + }); + + describe('list', () => { + it('mentions ignored dependencies', () => { + const scenario = getScenario(); + listCli(scenario.config, scenario.disk); + expect(scenario.log.mock.calls).toEqual([ + [expect.stringMatching(/Version Group 1/)], + ['- bar is ignored in this version group'], + [expect.stringMatching(/Default Version Group/)], + ['- foo 0.1.0'], + ]); + expect(scenario.disk.process.exit).not.toHaveBeenCalled(); + }); + }); + + describe('set-semver-ranges', () => { + // + }); +}); diff --git a/test/scenarios/version-is-ignored.ts b/test/scenarios/version-is-ignored.ts deleted file mode 100644 index 586ef364..00000000 --- a/test/scenarios/version-is-ignored.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { mockPackage } from '../mock'; -import { createScenario } from './lib/create-scenario'; - -/** - * - A does not depend on `bar` - * - B does depend on `bar` - * - `bar` is ignored by syncpack in every package - * - `bar` is unprotected so can mismatch etc - */ -export function versionIsIgnored() { - return createScenario( - [ - { - path: 'packages/a/package.json', - before: mockPackage('a', { deps: ['foo@0.1.0', 'bar@1.1.1'] }), - after: mockPackage('a', { deps: ['foo@0.1.0', 'bar@1.1.1'] }), - }, - { - path: 'packages/b/package.json', - before: mockPackage('b', { deps: ['bar@0.2.0'] }), - after: mockPackage('b', { deps: ['bar@0.2.0'] }), - }, - ], - { - versionGroups: [ - { - dependencies: ['bar'], - dependencyTypes: [], - packages: ['**'], - isIgnored: true, - }, - ], - }, - ); -}