diff --git a/lib/commands/shrinkwrap.js b/lib/commands/shrinkwrap.js
index 722d26c90dc73..42489a27f5bfb 100644
--- a/lib/commands/shrinkwrap.js
+++ b/lib/commands/shrinkwrap.js
@@ -1,7 +1,5 @@
 const { resolve, basename } = require('path')
-const util = require('util')
-const fs = require('fs')
-const { unlink } = fs.promises || { unlink: util.promisify(fs.unlink) }
+const { unlink } = require('fs').promises
 const Arborist = require('@npmcli/arborist')
 const log = require('npmlog')
 
@@ -21,7 +19,6 @@ class Shrinkwrap extends BaseCommand {
     // if has a npm-shrinkwrap.json, nothing to do
     // if has a package-lock.json, rename to npm-shrinkwrap.json
     // if has neither, load the actual tree and save that as npm-shrinkwrap.json
-    // in all cases, re-cast to current lockfile version
     //
     // loadVirtual, fall back to loadActual
     // rename shrinkwrap file type, and tree.meta.save()
@@ -40,17 +37,37 @@ class Shrinkwrap extends BaseCommand {
     const oldFilename = meta.filename
     const notSW = !newFile && basename(oldFilename) !== 'npm-shrinkwrap.json'
 
+    // The computed lockfile version of a hidden lockfile is always 3
+    // even if the actual value of the property is a different.
+    // When shrinkwrap is run with only a hidden lockfile we want to
+    // set the shrinkwrap lockfile version as whatever was explicitly
+    // requested with a fallback to the actual value from the hidden
+    // lockfile.
+    if (meta.hiddenLockfile) {
+      meta.lockfileVersion = arb.options.lockfileVersion ||
+        meta.originalLockfileVersion
+    }
     meta.hiddenLockfile = false
     meta.filename = sw
     await meta.save()
 
-    if (newFile)
-      log.notice('', 'created a lockfile as npm-shrinkwrap.json')
-    else if (notSW) {
+    const updatedVersion = meta.originalLockfileVersion !== meta.lockfileVersion
+      ? meta.lockfileVersion
+      : null
+
+    if (newFile) {
+      let message = 'created a lockfile as npm-shrinkwrap.json'
+      if (updatedVersion)
+        message += ` with version ${updatedVersion}`
+      log.notice('', message)
+    } else if (notSW) {
       await unlink(oldFilename)
-      log.notice('', 'package-lock.json has been renamed to npm-shrinkwrap.json')
-    } else if (meta.originalLockfileVersion !== this.npm.lockfileVersion)
-      log.notice('', `npm-shrinkwrap.json updated to version ${this.npm.lockfileVersion}`)
+      let message = 'package-lock.json has been renamed to npm-shrinkwrap.json'
+      if (updatedVersion)
+        message += ` and updated to version ${updatedVersion}`
+      log.notice('', message)
+    } else if (updatedVersion)
+      log.notice('', `npm-shrinkwrap.json updated to version ${updatedVersion}`)
     else
       log.notice('', 'npm-shrinkwrap.json up to date')
   }
diff --git a/tap-snapshots/test/lib/commands/shrinkwrap.js.test.cjs b/tap-snapshots/test/lib/commands/shrinkwrap.js.test.cjs
new file mode 100644
index 0000000000000..a0d5795776d6f
--- /dev/null
+++ b/tap-snapshots/test/lib/commands/shrinkwrap.js.test.cjs
@@ -0,0 +1,396 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below.  Do not ignore changes!
+ */
+'use strict'
+exports[`test/lib/commands/shrinkwrap.js TAP with hidden lockfile ancient > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "node_modules": {
+      ".package-lock.json": {
+        "lockfileVersion": 1
+      }
+    }
+  },
+  "config": {},
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-hidden-lockfile-ancient",
+    "lockfileVersion": 1,
+    "requires": true
+  },
+  "logs": [
+    "created a lockfile as npm-shrinkwrap.json"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with hidden lockfile ancient upgrade > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "node_modules": {
+      ".package-lock.json": {
+        "lockfileVersion": 1
+      }
+    }
+  },
+  "config": {
+    "lockfileVersion": 3
+  },
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-hidden-lockfile-ancient-upgrade",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {}
+  },
+  "logs": [
+    "created a lockfile as npm-shrinkwrap.json with version 3"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with hidden lockfile existing > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "node_modules": {
+      ".package-lock.json": {
+        "lockfileVersion": 2
+      }
+    }
+  },
+  "config": {},
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-hidden-lockfile-existing",
+    "lockfileVersion": 2,
+    "requires": true,
+    "packages": {}
+  },
+  "logs": [
+    "created a lockfile as npm-shrinkwrap.json"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with hidden lockfile existing downgrade > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "node_modules": {
+      ".package-lock.json": {
+        "lockfileVersion": 2
+      }
+    }
+  },
+  "config": {
+    "lockfileVersion": 1
+  },
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-hidden-lockfile-existing-downgrade",
+    "lockfileVersion": 1,
+    "requires": true
+  },
+  "logs": [
+    "created a lockfile as npm-shrinkwrap.json with version 1"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with hidden lockfile existing upgrade > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "node_modules": {
+      ".package-lock.json": {
+        "lockfileVersion": 2
+      }
+    }
+  },
+  "config": {
+    "lockfileVersion": 3
+  },
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-hidden-lockfile-existing-upgrade",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {}
+  },
+  "logs": [
+    "created a lockfile as npm-shrinkwrap.json with version 3"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with nothing ancient > must match snapshot 1`] = `
+{
+  "localPrefix": {},
+  "config": {},
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-nothing-ancient",
+    "lockfileVersion": 2,
+    "requires": true,
+    "packages": {}
+  },
+  "logs": [
+    "created a lockfile as npm-shrinkwrap.json with version 2"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with nothing ancient upgrade > must match snapshot 1`] = `
+{
+  "localPrefix": {},
+  "config": {
+    "lockfileVersion": 3
+  },
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-nothing-ancient-upgrade",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {}
+  },
+  "logs": [
+    "created a lockfile as npm-shrinkwrap.json with version 3"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with npm-shrinkwrap.json ancient > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "npm-shrinkwrap.json": {
+      "lockfileVersion": 1
+    }
+  },
+  "config": {},
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-ancient",
+    "lockfileVersion": 2,
+    "requires": true,
+    "packages": {
+      "": {
+        "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-ancient"
+      }
+    }
+  },
+  "logs": [
+    "npm-shrinkwrap.json updated to version 2"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with npm-shrinkwrap.json ancient upgrade > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "npm-shrinkwrap.json": {
+      "lockfileVersion": 1
+    }
+  },
+  "config": {
+    "lockfileVersion": 3
+  },
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-ancient-upgrade",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {
+      "": {
+        "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-ancient-upgrade"
+      }
+    }
+  },
+  "logs": [
+    "npm-shrinkwrap.json updated to version 3"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with npm-shrinkwrap.json existing > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "npm-shrinkwrap.json": {
+      "lockfileVersion": 2
+    }
+  },
+  "config": {},
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-existing",
+    "lockfileVersion": 2,
+    "requires": true,
+    "packages": {
+      "": {
+        "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-existing"
+      }
+    }
+  },
+  "logs": [
+    "npm-shrinkwrap.json up to date"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with npm-shrinkwrap.json existing downgrade > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "npm-shrinkwrap.json": {
+      "lockfileVersion": 2
+    }
+  },
+  "config": {
+    "lockfileVersion": 1
+  },
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-existing-downgrade",
+    "lockfileVersion": 1,
+    "requires": true
+  },
+  "logs": [
+    "npm-shrinkwrap.json updated to version 1"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with npm-shrinkwrap.json existing upgrade > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "npm-shrinkwrap.json": {
+      "lockfileVersion": 2
+    }
+  },
+  "config": {
+    "lockfileVersion": 3
+  },
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-existing-upgrade",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {
+      "": {
+        "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-existing-upgrade"
+      }
+    }
+  },
+  "logs": [
+    "npm-shrinkwrap.json updated to version 3"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with package-lock.json ancient > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "package-lock.json": {
+      "lockfileVersion": 1
+    }
+  },
+  "config": {},
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-package-lock.json-ancient",
+    "lockfileVersion": 2,
+    "requires": true,
+    "packages": {
+      "": {
+        "name": "tap-testdir-shrinkwrap-with-package-lock.json-ancient"
+      }
+    }
+  },
+  "logs": [
+    "package-lock.json has been renamed to npm-shrinkwrap.json and updated to version 2"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with package-lock.json ancient upgrade > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "package-lock.json": {
+      "lockfileVersion": 1
+    }
+  },
+  "config": {
+    "lockfileVersion": 3
+  },
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-package-lock.json-ancient-upgrade",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {
+      "": {
+        "name": "tap-testdir-shrinkwrap-with-package-lock.json-ancient-upgrade"
+      }
+    }
+  },
+  "logs": [
+    "package-lock.json has been renamed to npm-shrinkwrap.json and updated to version 3"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with package-lock.json existing > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "package-lock.json": {
+      "lockfileVersion": 2
+    }
+  },
+  "config": {},
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-package-lock.json-existing",
+    "lockfileVersion": 2,
+    "requires": true,
+    "packages": {
+      "": {
+        "name": "tap-testdir-shrinkwrap-with-package-lock.json-existing"
+      }
+    }
+  },
+  "logs": [
+    "package-lock.json has been renamed to npm-shrinkwrap.json"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with package-lock.json existing downgrade > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "package-lock.json": {
+      "lockfileVersion": 2
+    }
+  },
+  "config": {
+    "lockfileVersion": 1
+  },
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-package-lock.json-existing-downgrade",
+    "lockfileVersion": 1,
+    "requires": true
+  },
+  "logs": [
+    "package-lock.json has been renamed to npm-shrinkwrap.json and updated to version 1"
+  ]
+}
+`
+
+exports[`test/lib/commands/shrinkwrap.js TAP with package-lock.json existing upgrade > must match snapshot 1`] = `
+{
+  "localPrefix": {
+    "package-lock.json": {
+      "lockfileVersion": 2
+    }
+  },
+  "config": {
+    "lockfileVersion": 3
+  },
+  "shrinkwrap": {
+    "name": "tap-testdir-shrinkwrap-with-package-lock.json-existing-upgrade",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {
+      "": {
+        "name": "tap-testdir-shrinkwrap-with-package-lock.json-existing-upgrade"
+      }
+    }
+  },
+  "logs": [
+    "package-lock.json has been renamed to npm-shrinkwrap.json and updated to version 3"
+  ]
+}
+`
diff --git a/test/lib/commands/shrinkwrap.js b/test/lib/commands/shrinkwrap.js
index a1638ed5acfa1..112aa0a28a29b 100644
--- a/test/lib/commands/shrinkwrap.js
+++ b/test/lib/commands/shrinkwrap.js
@@ -1,330 +1,207 @@
 const t = require('tap')
 const fs = require('fs')
-const { fake: mockNpm } = require('../../fixtures/mock-npm')
-
-const config = {
-  global: false,
-}
-const flatOptions = {
-  depth: 0,
-}
-const npm = mockNpm({
-  config,
-  flatOptions,
-  lockfileVersion: 2,
-  globalDir: '',
-  prefix: '',
-})
-const tree = {
-  meta: {
-    hiddenLockfile: null,
-    loadedFromDisk: false,
-    filename: '',
-    originalLockfileVersion: 2,
-    save () {},
-  },
-}
-const mocks = {
-  npmlog: { notice () {} },
-  '@npmcli/arborist': class {
-    loadVirtual () {
-      return tree
-    }
-
-    loadActual () {
-      return tree
-    }
-  },
-  '../../../lib/utils/usage.js': () => 'usage instructions',
-  '../../../lib/utils/config/definitions.js': {},
-}
-
-t.afterEach(() => {
-  npm.prefix = ''
-  config.global = false
-  npm.globalDir = ''
-})
-
-t.test('no args', async t => {
-  t.plan(4)
-
-  npm.prefix = '/project/a'
-
-  class Arborist {
-    constructor (args) {
-      t.same(
-        args,
-        { ...flatOptions, path: npm.prefix },
-        'should call arborist constructor with expected args'
-      )
-    }
-
-    async loadVirtual () {
-      t.ok('should load virtual tree')
-      return {
-        ...tree,
-        meta: {
-          ...tree.meta,
-          save () {
-            t.ok('should save the lockfile')
-          },
-        },
-      }
-    }
-  }
-
-  const npmlog = {
-    notice (title, msg) {
-      t.equal(
-        msg,
-        'created a lockfile as npm-shrinkwrap.json',
-        'should log notice msg that file was successfully created'
-      )
-    },
-  }
-
-  const Shrinkwrap = t.mock('../../../lib/commands/shrinkwrap.js', {
-    ...mocks,
-    npmlog,
-    '@npmcli/arborist': Arborist,
-  })
-  const shrinkwrap = new Shrinkwrap(npm)
-
-  await shrinkwrap.exec([])
-})
-
-t.test('no virtual tree', async t => {
-  t.plan(4)
-
-  npm.prefix = '/project/a'
-
-  class Arborist {
-    constructor (args) {
-      t.same(
-        args,
-        { ...flatOptions, path: npm.prefix },
-        'should call arborist constructor with expected args'
-      )
-    }
-
-    async loadVirtual () {
-      throw new Error('ERR')
-    }
-
-    async loadActual () {
-      t.ok('should load actual tree')
-      return {
-        ...tree,
-        meta: {
-          ...tree.meta,
-          save () {
-            t.ok('should save the lockfile')
-          },
-        },
-      }
-    }
+const { resolve } = require('path')
+const { real: mockNpm } = require('../../fixtures/mock-npm')
+
+// Attempt to parse json values in snapshots before
+// stringifying to remove escaped values like \\"
+// This also doesn't reorder the keys of the object
+// like tap does by default which is nice in this case
+t.formatSnapshot = (obj) => JSON.stringify(obj, (k, v) => {
+  try {
+    return JSON.parse(v)
+  } catch (_) {}
+  return v
+}, 2)
+
+// Run shrinkwrap against a specified testdir with config items
+// and make some assertions that should always be true. Sets
+// the results on t.context for use in child tests
+const shrinkwrap = async (
+  t,
+  testdir = {},
+  config = {},
+  mocks = {}
+) => {
+  const { Npm, logs } = mockNpm(t, mocks)
+  const npm = new Npm()
+  await npm.load()
+
+  npm.localPrefix = t.testdir(testdir)
+  if (config.lockfileVersion)
+    npm.config.set('lockfile-version', config.lockfileVersion)
+  if (config.global)
+    npm.config.set('global', config.global)
+
+  await npm.exec('shrinkwrap', [])
+
+  const newFile = resolve(npm.localPrefix, 'npm-shrinkwrap.json')
+  const oldFile = resolve(npm.localPrefix, 'package-lock.json')
+  const notices = logs
+    .filter(([title]) => title === 'notice')
+    .map(([,, msg]) => msg)
+  const warnings = logs
+    .filter(([title]) => title === 'warn')
+    .map(([,, msg]) => msg)
+
+  t.notOk(fs.existsSync(oldFile), 'package-lock is always deleted')
+  t.same(warnings, [], 'no warnings')
+  t.teardown(() => delete t.context)
+  t.context = {
+    localPrefix: testdir,
+    config,
+    shrinkwrap: JSON.parse(fs.readFileSync(newFile)),
+    logs: notices,
   }
+}
 
-  const npmlog = {
-    notice (title, msg) {
-      t.equal(
-        msg,
-        'created a lockfile as npm-shrinkwrap.json',
-        'should log notice msg that file was successfully created'
-      )
-    },
+// Run shrinkwrap against all combinations of existing and config
+// lockfile versions
+const shrinkwrapMatrix = async (t, file, assertions) => {
+  const ancient = JSON.stringify({ lockfileVersion: 1 })
+  const existing = JSON.stringify({ lockfileVersion: 2 })
+  const upgrade = { lockfileVersion: 3 }
+  const downgrade = { lockfileVersion: 1 }
+
+  let ancientDir = {}
+  let existingDir = null
+  if (file === 'package-lock') {
+    ancientDir = { 'package-lock.json': ancient }
+    existingDir = { 'package-lock.json': existing }
+  } else if (file === 'npm-shrinkwrap') {
+    ancientDir = { 'npm-shrinkwrap.json': ancient }
+    existingDir = { 'npm-shrinkwrap.json': existing }
+  } else if (file === 'hidden-lockfile') {
+    ancientDir = { node_modules: { '.package-lock.json': ancient } }
+    existingDir = { node_modules: { '.package-lock.json': existing } }
   }
 
-  const Shrinkwrap = t.mock('../../../lib/commands/shrinkwrap.js', {
-    ...mocks,
-    npmlog,
-    '@npmcli/arborist': Arborist,
+  await t.test('ancient', async (t) => {
+    await shrinkwrap(t, ancientDir)
+    t.match(t.context, assertions.ancient)
+    t.matchSnapshot(t.context)
   })
-  const shrinkwrap = new Shrinkwrap(npm)
-
-  await shrinkwrap.exec([])
-})
-
-t.test('existing package-json file', async t => {
-  t.plan(5)
-
-  npm.prefix = '/project/a'
-
-  class Arborist {
-    constructor (args) {
-      t.same(
-        args,
-        { ...flatOptions, path: npm.prefix },
-        'should call arborist constructor with expected args'
-      )
-    }
-
-    async loadVirtual () {
-      t.ok('should load virtual tree')
-      return {
-        ...tree,
-        meta: {
-          hiddenLockfile: false,
-          loadedFromDisk: true,
-          filename: 'package-lock.json',
-          save () {
-            t.ok('should save the lockfile')
-          },
-        },
-      }
-    }
-  }
-
-  const npmlog = {
-    notice (title, msg) {
-      t.equal(
-        msg,
-        'package-lock.json has been renamed to npm-shrinkwrap.json',
-        'should log notice msg that file was renamed'
-      )
-    },
-  }
-
-  const fs = {
-    promises: {
-      unlink (filename) {
-        t.equal(filename, 'package-lock.json', 'should remove old lockfile')
-      },
-    },
-  }
-
-  const Shrinkwrap = t.mock('../../../lib/commands/shrinkwrap.js', {
-    ...mocks,
-    fs,
-    npmlog,
-    '@npmcli/arborist': Arborist,
+  await t.test('ancient upgrade', async (t) => {
+    await shrinkwrap(t, ancientDir, upgrade)
+    t.match(t.context, assertions.ancientUpgrade)
+    t.matchSnapshot(t.context)
   })
-  const shrinkwrap = new Shrinkwrap(npm)
-
-  await shrinkwrap.exec([])
-})
-
-t.test('update shrinkwrap file version', async t => {
-  t.plan(4)
 
-  npm.prefix = '/project/a'
-
-  class Arborist {
-    constructor (args) {
-      t.same(
-        args,
-        { ...flatOptions, path: npm.prefix },
-        'should call arborist constructor with expected args'
-      )
-    }
-
-    async loadVirtual () {
-      t.ok('should load virtual tree')
-      return {
-        ...tree,
-        meta: {
-          hiddenLockfile: false,
-          loadedFromDisk: true,
-          filename: 'npm-shrinkwrap.json',
-          originalLockfileVersion: 1,
-          save () {
-            t.ok('should save the lockfile')
-          },
-        },
-      }
-    }
-  }
-
-  const npmlog = {
-    notice (title, msg) {
-      t.equal(
-        msg,
-        'npm-shrinkwrap.json updated to version 2',
-        'should log notice msg that file was updated'
-      )
-    },
-  }
-
-  const Shrinkwrap = t.mock('../../../lib/commands/shrinkwrap.js', {
-    ...mocks,
-    npmlog,
-    '@npmcli/arborist': Arborist,
-  })
-  const shrinkwrap = new Shrinkwrap(npm)
-
-  await shrinkwrap.exec([])
-})
-
-t.test('update to date shrinkwrap file', async t => {
-  t.plan(4)
-
-  npm.prefix = '/project/a'
-
-  class Arborist {
-    constructor (args) {
-      t.same(
-        args,
-        { ...flatOptions, path: npm.prefix },
-        'should call arborist constructor with expected args'
-      )
-    }
-
-    async loadVirtual () {
-      t.ok('should load virtual tree')
-      return {
-        ...tree,
-        meta: {
-          hiddenLockfile: false,
-          loadedFromDisk: true,
-          filename: 'npm-shrinkwrap.json',
-          originalLockfileVersion: 2,
-          save () {
-            t.ok('should save the lockfile')
-          },
-        },
-      }
-    }
-  }
-
-  const npmlog = {
-    notice (title, msg) {
-      t.equal(
-        msg,
-        'npm-shrinkwrap.json up to date',
-        'should log notice msg shrinkwrap up to date'
-      )
-    },
+  if (existingDir) {
+    await t.test('existing', async (t) => {
+      await shrinkwrap(t, existingDir)
+      t.match(t.context, assertions.existing)
+      t.matchSnapshot(t.context)
+    })
+    await t.test('existing upgrade', async (t) => {
+      await shrinkwrap(t, existingDir, upgrade)
+      t.match(t.context, assertions.existingUpgrade)
+      t.matchSnapshot(t.context)
+    })
+    await t.test('existing downgrade', async (t) => {
+      await shrinkwrap(t, existingDir, downgrade)
+      t.match(t.context, assertions.existingDowngrade)
+      t.matchSnapshot(t.context)
+    })
   }
+}
 
-  const Shrinkwrap = t.mock('../../../lib/commands/shrinkwrap.js', {
-    ...mocks,
-    npmlog,
-    '@npmcli/arborist': Arborist,
-  })
-  const shrinkwrap = new Shrinkwrap(npm)
-
-  await shrinkwrap.exec([])
-})
+const NOTICES = {
+  CREATED: (v = '') =>
+    [`created a lockfile as npm-shrinkwrap.json${v && ` with version ${v}`}`],
+  RENAMED: (v = '') =>
+    [`package-lock.json has been renamed to npm-shrinkwrap.json${v && ` and updated to version ${v}`}`],
+  UPDATED: (v = '') =>
+    [`npm-shrinkwrap.json updated to version ${v}`],
+  SAME: () =>
+    [`npm-shrinkwrap.json up to date`],
+}
 
-t.test('shrinkwrap --global', async t => {
-  const Shrinkwrap = t.mock('../../../lib/commands/shrinkwrap.js', mocks)
+t.test('with nothing', t => shrinkwrapMatrix(t, null, {
+  ancient: {
+    shrinkwrap: { lockfileVersion: 2 },
+    logs: NOTICES.CREATED(2),
+  },
+  ancientUpgrade: {
+    shrinkwrap: { lockfileVersion: 3 },
+    logs: NOTICES.CREATED(3),
+  },
+}))
 
-  config.global = true
-  const shrinkwrap = new Shrinkwrap(npm)
+t.test('with package-lock.json', t => shrinkwrapMatrix(t, 'package-lock', {
+  ancient: {
+    shrinkwrap: { lockfileVersion: 2 },
+    logs: NOTICES.RENAMED(2),
+  },
+  ancientUpgrade: {
+    shrinkwrap: { lockfileVersion: 3 },
+    logs: NOTICES.RENAMED(3),
+  },
+  existing: {
+    shrinkwrap: { lockfileVersion: 2 },
+    logs: NOTICES.RENAMED(),
+  },
+  existingUpgrade: {
+    shrinkwrap: { lockfileVersion: 3 },
+    logs: NOTICES.RENAMED(3),
+  },
+  existingDowngrade: {
+    shrinkwrap: { lockfileVersion: 1 },
+    logs: NOTICES.RENAMED(1),
+  },
+}))
 
-  await t.rejects(
-    shrinkwrap.exec([]),
-    { code: 'ESHRINKWRAPGLOBAL', message: /does not work for global packages/ },
-    'should throw no global support msg'
-  )
-})
+t.test('with npm-shrinkwrap.json', t => shrinkwrapMatrix(t, 'npm-shrinkwrap', {
+  ancient: {
+    shrinkwrap: { lockfileVersion: 2 },
+    logs: NOTICES.UPDATED(2),
+  },
+  ancientUpgrade: {
+    shrinkwrap: { lockfileVersion: 3 },
+    logs: NOTICES.UPDATED(3),
+  },
+  existing: {
+    shrinkwrap: { lockfileVersion: 2 },
+    logs: NOTICES.SAME(),
+  },
+  existingUpgrade: {
+    shrinkwrap: { lockfileVersion: 3 },
+    logs: NOTICES.UPDATED(3),
+  },
+  existingDowngrade: {
+    shrinkwrap: { lockfileVersion: 1 },
+    logs: NOTICES.UPDATED(1),
+  },
+}))
 
-t.test('works without fs.promises', async t => {
-  t.doesNotThrow(() => {
-    const Shrinkwrap = t.mock('../../../lib/commands/shrinkwrap.js', {
-      ...mocks,
-      fs: { ...fs, promises: null },
-    })
-    new Shrinkwrap(npm)
+t.test('with hidden lockfile', t => shrinkwrapMatrix(t, 'hidden-lockfile', {
+  ancient: {
+    shrinkwrap: { lockfileVersion: 1 },
+    logs: NOTICES.CREATED(),
+  },
+  ancientUpgrade: {
+    shrinkwrap: { lockfileVersion: 3 },
+    logs: NOTICES.CREATED(),
+  },
+  existing: {
+    shrinkwrap: { lockfileVersion: 2 },
+    logs: NOTICES.CREATED(),
+  },
+  existingUpgrade: {
+    shrinkwrap: { lockfileVersion: 3 },
+    logs: NOTICES.CREATED(3),
+  },
+  existingDowngrade: {
+    shrinkwrap: { lockfileVersion: 1 },
+    logs: NOTICES.CREATED(1),
+  },
+}))
+
+t.test('throws in global mode', async t => {
+  t.rejects(shrinkwrap(t, {}, {
+    global: true,
+  }), {
+    message: '`npm shrinkwrap` does not work for global packages',
+    code: 'ESHRINKWRAPGLOBAL',
   })
 })