@mutineerjs/mutineer 0.2.4 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -42
- package/dist/bin/mutineer.js +0 -0
- package/dist/mutators/__tests__/operator.spec.js +169 -0
- package/dist/mutators/__tests__/registry.spec.js +6 -0
- package/dist/mutators/__tests__/return-value.spec.js +239 -0
- package/dist/mutators/__tests__/utils.spec.js +68 -1
- package/dist/mutators/operator.d.ts +25 -0
- package/dist/mutators/operator.js +50 -0
- package/dist/mutators/registry.d.ts +6 -28
- package/dist/mutators/registry.js +14 -66
- package/dist/mutators/return-value.d.ts +39 -0
- package/dist/mutators/return-value.js +104 -0
- package/dist/mutators/utils.d.ts +21 -0
- package/dist/mutators/utils.js +44 -27
- package/dist/runner/__tests__/pool-executor.spec.js +2 -4
- package/dist/runner/__tests__/variants.spec.js +3 -1
- package/dist/runner/discover.js +2 -1
- package/dist/runner/jest/__tests__/adapter.spec.js +1 -1
- package/dist/runner/jest/__tests__/pool.spec.js +5 -6
- package/dist/runner/jest/__tests__/worker-runtime.spec.js +2 -2
- package/dist/runner/jest/adapter.js +1 -1
- package/dist/runner/jest/pool.d.ts +1 -1
- package/dist/runner/jest/pool.js +6 -6
- package/dist/runner/jest/worker.mjs +1 -1
- package/dist/runner/pool-executor.js +1 -1
- package/dist/runner/shared/__tests__/redirect-state.spec.js +3 -3
- package/dist/runner/shared/index.d.ts +1 -1
- package/dist/runner/shared/index.js +1 -1
- package/dist/runner/shared/redirect-state.d.ts +2 -2
- package/dist/runner/shared/redirect-state.js +4 -4
- package/dist/runner/types.d.ts +1 -1
- package/dist/runner/vitest/__tests__/adapter.spec.js +1 -1
- package/dist/runner/vitest/__tests__/pool.spec.js +1 -1
- package/dist/runner/vitest/__tests__/redirect-loader.spec.js +83 -1
- package/dist/runner/vitest/__tests__/worker-runtime.spec.js +84 -0
- package/dist/runner/vitest/adapter.js +1 -1
- package/dist/runner/vitest/index.d.ts +0 -1
- package/dist/runner/vitest/index.js +0 -1
- package/dist/runner/vitest/pool.d.ts +1 -1
- package/dist/runner/vitest/pool.js +7 -7
- package/dist/runner/vitest/redirect-loader.d.ts +1 -1
- package/dist/runner/vitest/redirect-loader.js +1 -1
- package/dist/runner/vitest/worker-runtime.js +3 -3
- package/dist/runner/vitest/worker.mjs +1 -1
- package/dist/utils/__tests__/coverage.spec.js +167 -0
- package/dist/utils/__tests__/progress.spec.js +96 -0
- package/package.json +71 -22
- package/dist/admin/assets/index-B7nXq-e7.js +0 -32
- package/dist/admin/assets/index-B7nXq-e7.js.map +0 -1
- package/dist/admin/assets/index-BDQLkBUE.js +0 -32
- package/dist/admin/assets/index-BDQLkBUE.js.map +0 -1
- package/dist/admin/assets/index-DVkP-Tc7.css +0 -1
- package/dist/admin/index.html +0 -13
- package/dist/admin/server/admin.d.ts +0 -6
- package/dist/admin/server/admin.js +0 -234
- package/dist/bin/mutate-vitest.d.ts +0 -2
- package/dist/bin/mutate-vitest.js +0 -90
- package/dist/plugin/viteMutate.d.ts +0 -15
- package/dist/plugin/viteMutate.js +0 -52
- package/dist/plugin/vitest.setup.d.ts +0 -47
- package/dist/plugin/vitest.setup.js +0 -118
- package/dist/plugin/withVitest.d.ts +0 -13
- package/dist/plugin/withVitest.js +0 -30
- package/dist/runner/__tests__/orchestrator.spec.js +0 -55
- package/dist/runner/adapters/__tests__/jest.spec.js +0 -88
- package/dist/runner/adapters/__tests__/vitest-worker-runtime.spec.d.ts +0 -1
- package/dist/runner/adapters/__tests__/vitest-worker-runtime.spec.js +0 -59
- package/dist/runner/adapters/__tests__/vitest.spec.d.ts +0 -1
- package/dist/runner/adapters/__tests__/vitest.spec.js +0 -118
- package/dist/runner/adapters/index.d.ts +0 -10
- package/dist/runner/adapters/index.js +0 -9
- package/dist/runner/adapters/jest/__tests__/index.spec.d.ts +0 -1
- package/dist/runner/adapters/jest/__tests__/index.spec.js +0 -88
- package/dist/runner/adapters/jest/index.d.ts +0 -24
- package/dist/runner/adapters/jest/index.js +0 -216
- package/dist/runner/adapters/jest/worker-runtime.d.ts +0 -37
- package/dist/runner/adapters/jest/worker-runtime.js +0 -171
- package/dist/runner/adapters/jest-worker-runtime.d.ts +0 -37
- package/dist/runner/adapters/jest-worker-runtime.js +0 -171
- package/dist/runner/adapters/jest.d.ts +0 -24
- package/dist/runner/adapters/jest.js +0 -216
- package/dist/runner/adapters/types.d.ts +0 -89
- package/dist/runner/adapters/types.js +0 -8
- package/dist/runner/adapters/vitest/__tests__/index.spec.d.ts +0 -1
- package/dist/runner/adapters/vitest/__tests__/index.spec.js +0 -118
- package/dist/runner/adapters/vitest/__tests__/worker-runtime.spec.d.ts +0 -1
- package/dist/runner/adapters/vitest/__tests__/worker-runtime.spec.js +0 -59
- package/dist/runner/adapters/vitest/index.d.ts +0 -33
- package/dist/runner/adapters/vitest/index.js +0 -267
- package/dist/runner/adapters/vitest/worker-runtime.d.ts +0 -25
- package/dist/runner/adapters/vitest/worker-runtime.js +0 -118
- package/dist/runner/adapters/vitest-worker-runtime.d.ts +0 -25
- package/dist/runner/adapters/vitest-worker-runtime.js +0 -118
- package/dist/runner/adapters/vitest.d.ts +0 -33
- package/dist/runner/adapters/vitest.js +0 -267
- package/dist/runner/pool/__tests__/index.spec.d.ts +0 -1
- package/dist/runner/pool/__tests__/index.spec.js +0 -83
- package/dist/runner/pool/__tests__/pool-plugin.spec.d.ts +0 -1
- package/dist/runner/pool/__tests__/pool-plugin.spec.js +0 -59
- package/dist/runner/pool/__tests__/pool-redirect-loader.spec.d.ts +0 -1
- package/dist/runner/pool/__tests__/pool-redirect-loader.spec.js +0 -78
- package/dist/runner/pool/index.d.ts +0 -8
- package/dist/runner/pool/index.js +0 -9
- package/dist/runner/pool/jest/pool.d.ts +0 -52
- package/dist/runner/pool/jest/pool.js +0 -309
- package/dist/runner/pool/jest/worker.d.mts +0 -1
- package/dist/runner/pool/jest/worker.mjs +0 -60
- package/dist/runner/pool/jest-pool.d.ts +0 -52
- package/dist/runner/pool/jest-pool.js +0 -309
- package/dist/runner/pool/jest-worker.d.mts +0 -1
- package/dist/runner/pool/jest-worker.mjs +0 -60
- package/dist/runner/pool/plugin.d.ts +0 -18
- package/dist/runner/pool/plugin.js +0 -60
- package/dist/runner/pool/pool-plugin.d.ts +0 -18
- package/dist/runner/pool/pool-plugin.js +0 -60
- package/dist/runner/pool/pool-redirect-loader.d.ts +0 -19
- package/dist/runner/pool/pool-redirect-loader.js +0 -116
- package/dist/runner/pool/pool-redirect-loader.mjs +0 -146
- package/dist/runner/pool/redirect-loader.d.ts +0 -19
- package/dist/runner/pool/redirect-loader.js +0 -116
- package/dist/runner/pool/vitest/pool.d.ts +0 -70
- package/dist/runner/pool/vitest/pool.js +0 -376
- package/dist/runner/pool/vitest/worker.d.mts +0 -15
- package/dist/runner/pool/vitest/worker.mjs +0 -96
- package/dist/runner/pool/vitest-worker.d.mts +0 -15
- package/dist/runner/pool/vitest-worker.mjs +0 -96
- package/dist/runner/shared-module-redirect.d.ts +0 -56
- package/dist/runner/shared-module-redirect.js +0 -84
- package/dist/types/api.d.ts +0 -20
- package/dist/types/api.js +0 -1
- /package/dist/{runner/__tests__/orchestrator.spec.d.ts → mutators/__tests__/operator.spec.d.ts} +0 -0
- /package/dist/{runner/adapters/__tests__/jest.spec.d.ts → mutators/__tests__/return-value.spec.d.ts} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@import"https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap";*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{color-scheme:dark}*{box-sizing:border-box}body{min-height:100vh;--tw-bg-opacity: 1;background-color:rgb(2 6 23 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity));font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;margin:0}#app{padding:2rem}a{color:inherit}button{font:inherit}.static{position:static}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-6{margin-bottom:1.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.w-12{width:3rem}.w-4{width:1rem}.w-full{width:100%}.min-w-full{min-width:100%}.max-w-6xl{max-width:72rem}.flex-1{flex:1 1 0%}.border-collapse{border-collapse:collapse}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-slate-700{--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}.border-slate-800{--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}.bg-green-500\/10{background-color:#22c55e1a}.bg-red-500\/10{background-color:#ef44441a}.bg-slate-900{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.bg-slate-900\/60{background-color:#0f172a99}.bg-slate-950{--tw-bg-opacity: 1;background-color:rgb(2 6 23 / var(--tw-bg-opacity))}.bg-slate-950\/60{background-color:#02061799}.bg-slate-950\/70{background-color:#020617b3}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-sky-500{--tw-gradient-from: #0ea5e9 var(--tw-gradient-from-position);--tw-gradient-to: rgb(14 165 233 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.to-indigo-500{--tw-gradient-to: #6366f1 var(--tw-gradient-to-position)}.p-1{padding:.25rem}.p-12{padding:3rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pl-0{padding-left:0}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-6{line-height:1.5rem}.tracking-\[0\.08em\]{letter-spacing:.08em}.tracking-\[0\.1em\]{letter-spacing:.1em}.tracking-\[0\.2em\]{letter-spacing:.2em}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity))}.text-sky-400{--tw-text-opacity: 1;color:rgb(56 189 248 / var(--tw-text-opacity))}.text-slate-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}.text-slate-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.text-slate-950{--tw-text-opacity: 1;color:rgb(2 6 23 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.placeholder\:text-slate-500::-moz-placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.placeholder\:text-slate-500::placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.even\:bg-slate-800\/30:nth-child(2n){background-color:#1e293b4d}.hover\:bg-slate-800\/40:hover{background-color:#1e293b66}.hover\:brightness-110:hover{--tw-brightness: brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.focus\:border-sky-400:focus{--tw-border-opacity: 1;border-color:rgb(56 189 248 / var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-sky-400:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(56 189 248 / var(--tw-ring-opacity))}.focus\:ring-sky-400\/60:focus{--tw-ring-color: rgb(56 189 248 / .6)}.disabled\:cursor-progress:disabled{cursor:progress}.disabled\:opacity-70:disabled{opacity:.7}@media (min-width: 640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:text-right{text-align:right}}@media (min-width: 768px){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}
|
package/dist/admin/index.html
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>Mutineer Admin</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-B7nXq-e7.js"></script>
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-DVkP-Tc7.css">
|
|
9
|
-
</head>
|
|
10
|
-
<body>
|
|
11
|
-
<div id="app"></div>
|
|
12
|
-
</body>
|
|
13
|
-
</html>
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import http from 'node:http';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import fs from 'node:fs/promises';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { diffLines } from 'diff';
|
|
6
|
-
import { readMutantCache } from '../../runner/orchestrator.js';
|
|
7
|
-
import { mutateModuleSource } from '../../core/module.js';
|
|
8
|
-
import { mutateVueSfcScriptSetup } from '../../core/sfc.js';
|
|
9
|
-
import { loadMutineerConfig } from '../../runner/config.js';
|
|
10
|
-
const mimeTypes = {
|
|
11
|
-
'.html': 'text/html; charset=utf-8',
|
|
12
|
-
'.js': 'application/javascript; charset=utf-8',
|
|
13
|
-
'.css': 'text/css; charset=utf-8',
|
|
14
|
-
'.json': 'application/json; charset=utf-8',
|
|
15
|
-
'.svg': 'image/svg+xml',
|
|
16
|
-
'.ico': 'image/x-icon',
|
|
17
|
-
'.png': 'image/png',
|
|
18
|
-
'.jpg': 'image/jpeg',
|
|
19
|
-
'.jpeg': 'image/jpeg',
|
|
20
|
-
'.map': 'application/json; charset=utf-8'
|
|
21
|
-
};
|
|
22
|
-
function toResultList(cache, cwd) {
|
|
23
|
-
return Object.entries(cache).map(([key, entry]) => ({
|
|
24
|
-
id: key,
|
|
25
|
-
relativePath: path.relative(cwd, entry.file) || entry.file,
|
|
26
|
-
...entry
|
|
27
|
-
}));
|
|
28
|
-
}
|
|
29
|
-
async function serveStatic(filePath, res) {
|
|
30
|
-
try {
|
|
31
|
-
const data = await fs.readFile(filePath);
|
|
32
|
-
const ext = path.extname(filePath);
|
|
33
|
-
res.statusCode = 200;
|
|
34
|
-
res.setHeader('Content-Type', mimeTypes[ext] ?? 'application/octet-stream');
|
|
35
|
-
res.setHeader('Cache-Control', ext === '.html' ? 'no-cache' : 'public, max-age=31536000, immutable');
|
|
36
|
-
res.end(data);
|
|
37
|
-
}
|
|
38
|
-
catch (err) {
|
|
39
|
-
const code = err.code;
|
|
40
|
-
if (code === 'ENOENT') {
|
|
41
|
-
res.statusCode = 404;
|
|
42
|
-
res.end('Not found');
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
res.statusCode = 500;
|
|
46
|
-
res.end('Internal server error');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
async function handleApiRequest(cwd, res) {
|
|
50
|
-
const cache = await readMutantCache(cwd);
|
|
51
|
-
const payload = { mutants: toResultList(cache, cwd) };
|
|
52
|
-
res.statusCode = 200;
|
|
53
|
-
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
54
|
-
res.setHeader('Cache-Control', 'no-store');
|
|
55
|
-
res.end(JSON.stringify(payload));
|
|
56
|
-
}
|
|
57
|
-
async function handleConfigRequest(cwd, res, configPath) {
|
|
58
|
-
res.statusCode = 200;
|
|
59
|
-
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
60
|
-
try {
|
|
61
|
-
const config = await loadMutineerConfig(cwd, configPath);
|
|
62
|
-
const payload = { config };
|
|
63
|
-
res.setHeader('Cache-Control', 'no-store');
|
|
64
|
-
res.end(JSON.stringify(payload));
|
|
65
|
-
}
|
|
66
|
-
catch (err) {
|
|
67
|
-
const message = err instanceof Error ? err.message : 'Unable to load config';
|
|
68
|
-
const payload = { error: message };
|
|
69
|
-
res.statusCode = 404;
|
|
70
|
-
res.end(JSON.stringify(payload));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
function ensureWithinCwd(cwd, target) {
|
|
74
|
-
const normalisedCwd = path.resolve(cwd);
|
|
75
|
-
const resolved = path.resolve(target);
|
|
76
|
-
const relative = path.relative(normalisedCwd, resolved);
|
|
77
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
78
|
-
throw new Error('Requested file is outside the project directory');
|
|
79
|
-
}
|
|
80
|
-
return resolved;
|
|
81
|
-
}
|
|
82
|
-
async function readFileFromProject(cwd, requestedPath) {
|
|
83
|
-
let resolved;
|
|
84
|
-
try {
|
|
85
|
-
resolved = ensureWithinCwd(cwd, requestedPath);
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
throw Object.assign(new Error('Access denied'), { status: 403 });
|
|
89
|
-
}
|
|
90
|
-
try {
|
|
91
|
-
const code = await fs.readFile(resolved, 'utf8');
|
|
92
|
-
return { resolved, code };
|
|
93
|
-
}
|
|
94
|
-
catch (err) {
|
|
95
|
-
const codeErr = err;
|
|
96
|
-
if (codeErr.code === 'ENOENT') {
|
|
97
|
-
throw Object.assign(new Error('File not found'), { status: 404 });
|
|
98
|
-
}
|
|
99
|
-
throw Object.assign(new Error('Unable to read file'), { status: 500 });
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function findVariantForMutator(filePath, code, mutator, line, col) {
|
|
103
|
-
const isVue = filePath.toLowerCase().endsWith('.vue');
|
|
104
|
-
const variants = isVue ? mutateVueSfcScriptSetup(filePath, code) : mutateModuleSource(code);
|
|
105
|
-
return variants.find(v => v.name === mutator && v.line === line && v.col === col);
|
|
106
|
-
}
|
|
107
|
-
function buildDiff(original, mutated) {
|
|
108
|
-
const parts = diffLines(original, mutated);
|
|
109
|
-
const lines = [];
|
|
110
|
-
let leftLine = 1;
|
|
111
|
-
let rightLine = 1;
|
|
112
|
-
for (const part of parts) {
|
|
113
|
-
const chunkLines = part.value.split(/\r?\n/);
|
|
114
|
-
if (chunkLines[chunkLines.length - 1] === '')
|
|
115
|
-
chunkLines.pop();
|
|
116
|
-
for (const chunk of chunkLines) {
|
|
117
|
-
if (part.added) {
|
|
118
|
-
lines.push({ type: 'add', rightNumber: rightLine++, text: chunk });
|
|
119
|
-
}
|
|
120
|
-
else if (part.removed) {
|
|
121
|
-
lines.push({ type: 'remove', leftNumber: leftLine++, text: chunk });
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
lines.push({
|
|
125
|
-
type: 'context',
|
|
126
|
-
leftNumber: leftLine++,
|
|
127
|
-
rightNumber: rightLine++,
|
|
128
|
-
text: chunk
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return { lines };
|
|
134
|
-
}
|
|
135
|
-
async function handleDiffRequest(cwd, url, res) {
|
|
136
|
-
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
137
|
-
const fileParam = url.searchParams.get('file');
|
|
138
|
-
const mutator = url.searchParams.get('mutator');
|
|
139
|
-
const lineParam = url.searchParams.get('line');
|
|
140
|
-
const colParam = url.searchParams.get('col');
|
|
141
|
-
if (!fileParam || !mutator || !lineParam || !colParam) {
|
|
142
|
-
res.statusCode = 400;
|
|
143
|
-
res.end(JSON.stringify({ error: 'Missing query parameters' }));
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const line = Number(lineParam);
|
|
147
|
-
const col = Number(colParam);
|
|
148
|
-
if (!Number.isFinite(line) || !Number.isFinite(col)) {
|
|
149
|
-
res.statusCode = 400;
|
|
150
|
-
res.end(JSON.stringify({ error: 'Invalid line or column' }));
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
let fileData;
|
|
154
|
-
try {
|
|
155
|
-
const joined = path.isAbsolute(fileParam) ? fileParam : path.join(cwd, fileParam);
|
|
156
|
-
fileData = await readFileFromProject(cwd, joined);
|
|
157
|
-
}
|
|
158
|
-
catch (err) {
|
|
159
|
-
const status = typeof err === 'object' && err && 'status' in err && typeof err.status === 'number'
|
|
160
|
-
? err.status
|
|
161
|
-
: 500;
|
|
162
|
-
const message = err instanceof Error ? err.message : 'Unable to rebuild mutation diff';
|
|
163
|
-
res.statusCode = status;
|
|
164
|
-
res.end(JSON.stringify({ error: message }));
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
const variant = findVariantForMutator(fileData.resolved, fileData.code, mutator, line, col);
|
|
168
|
-
if (!variant) {
|
|
169
|
-
res.statusCode = 404;
|
|
170
|
-
res.end(JSON.stringify({ error: 'Unable to rebuild mutation diff' }));
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const diff = buildDiff(fileData.code, variant.code);
|
|
174
|
-
res.statusCode = 200;
|
|
175
|
-
res.setHeader('Cache-Control', 'no-store');
|
|
176
|
-
res.end(JSON.stringify(diff));
|
|
177
|
-
}
|
|
178
|
-
export async function runAdminServer(cwd, options) {
|
|
179
|
-
const port = options?.port ?? 4177;
|
|
180
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
181
|
-
const staticDir = path.resolve(__dirname, '..');
|
|
182
|
-
const indexPath = path.join(staticDir, 'index.html');
|
|
183
|
-
try {
|
|
184
|
-
await fs.access(indexPath);
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
throw new Error(`Admin UI bundle not found at ${indexPath}. Run "npm run build:admin" before starting the server.`);
|
|
188
|
-
}
|
|
189
|
-
const server = http.createServer(async (req, res) => {
|
|
190
|
-
try {
|
|
191
|
-
if (!req.url) {
|
|
192
|
-
res.statusCode = 400;
|
|
193
|
-
res.end('Invalid request');
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
const url = new URL(req.url, `http://localhost:${port}`);
|
|
197
|
-
if (url.pathname === '/api/results') {
|
|
198
|
-
await handleApiRequest(cwd, res);
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
if (url.pathname === '/api/config') {
|
|
202
|
-
await handleConfigRequest(cwd, res, options?.configPath);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (url.pathname === '/api/diff') {
|
|
206
|
-
await handleDiffRequest(cwd, url, res);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
const decodedPath = decodeURIComponent(url.pathname);
|
|
210
|
-
let target = path.join(staticDir, decodedPath);
|
|
211
|
-
if (decodedPath === '/' || !path.extname(decodedPath)) {
|
|
212
|
-
target = path.join(staticDir, 'index.html');
|
|
213
|
-
}
|
|
214
|
-
await serveStatic(target, res);
|
|
215
|
-
}
|
|
216
|
-
catch (err) {
|
|
217
|
-
console.error('Admin server error:', err);
|
|
218
|
-
res.statusCode = 500;
|
|
219
|
-
res.end('Internal server error');
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
return new Promise((resolve, reject) => {
|
|
223
|
-
const stop = () => {
|
|
224
|
-
server.close(() => resolve());
|
|
225
|
-
};
|
|
226
|
-
server.once('error', reject);
|
|
227
|
-
process.once('SIGINT', stop);
|
|
228
|
-
process.once('SIGTERM', stop);
|
|
229
|
-
server.listen(port, () => {
|
|
230
|
-
console.log(`\nMutineer admin UI ready at http://localhost:${port}`);
|
|
231
|
-
console.log('Press Ctrl+C to stop the server.\n');
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
-
import { runOrchestrator } from '../runner/orchestrator.js';
|
|
6
|
-
// Constants
|
|
7
|
-
const DEFAULT_ADMIN_PORT = 4177;
|
|
8
|
-
const ADMIN_COMMAND = 'admin';
|
|
9
|
-
const CONFIG_FLAGS = ['--config', '-c'];
|
|
10
|
-
/**
|
|
11
|
-
* Resolves the path to the admin server module, preferring compiled JS over source TS
|
|
12
|
-
*/
|
|
13
|
-
function resolveAdminServerPath() {
|
|
14
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
const compiledPath = path.resolve(__dirname, '../admin/server/admin.js');
|
|
16
|
-
return fs.existsSync(compiledPath) ? compiledPath : path.resolve(__dirname, '../../admin/server/admin.ts');
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Dynamically imports and returns the admin server function
|
|
20
|
-
*/
|
|
21
|
-
async function loadAdminServer() {
|
|
22
|
-
const serverPath = resolveAdminServerPath();
|
|
23
|
-
const module = (await import(pathToFileURL(serverPath).href));
|
|
24
|
-
return module.runAdminServer;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Parses a numeric argument from argv, supporting both space-separated and inline formats
|
|
28
|
-
* @example '--port 3000' or '--port=3000'
|
|
29
|
-
*/
|
|
30
|
-
function parseNumericArg(argv, flag) {
|
|
31
|
-
// Space-separated format: --flag value
|
|
32
|
-
const idx = argv.findIndex(arg => arg === flag);
|
|
33
|
-
if (idx >= 0) {
|
|
34
|
-
const value = argv[idx + 1];
|
|
35
|
-
if (value) {
|
|
36
|
-
const parsed = Number(value);
|
|
37
|
-
if (Number.isFinite(parsed))
|
|
38
|
-
return parsed;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// Inline format: --flag=value
|
|
42
|
-
const prefix = `${flag}=`;
|
|
43
|
-
const inline = argv.find(arg => arg.startsWith(prefix));
|
|
44
|
-
if (inline) {
|
|
45
|
-
const parsed = Number(inline.slice(prefix.length));
|
|
46
|
-
if (Number.isFinite(parsed))
|
|
47
|
-
return parsed;
|
|
48
|
-
}
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Parses a string argument from argv, supporting both space-separated and inline formats
|
|
53
|
-
* @example '--config path.ts' or '--config=path.ts'
|
|
54
|
-
*/
|
|
55
|
-
function parseStringArg(argv, flags) {
|
|
56
|
-
// Space-separated format: --flag value
|
|
57
|
-
const idx = argv.findIndex(arg => flags.includes(arg));
|
|
58
|
-
if (idx >= 0) {
|
|
59
|
-
const value = argv[idx + 1];
|
|
60
|
-
if (value && !value.startsWith('-'))
|
|
61
|
-
return value;
|
|
62
|
-
}
|
|
63
|
-
// Inline format: --flag=value
|
|
64
|
-
for (const flag of flags) {
|
|
65
|
-
const prefix = `${flag}=`;
|
|
66
|
-
const inline = argv.find(arg => arg.startsWith(prefix));
|
|
67
|
-
if (inline) {
|
|
68
|
-
const value = inline.slice(prefix.length);
|
|
69
|
-
if (value)
|
|
70
|
-
return value;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return undefined;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Main entry point - routes to admin server or orchestrator
|
|
77
|
-
*/
|
|
78
|
-
async function main() {
|
|
79
|
-
const args = process.argv.slice(2);
|
|
80
|
-
if (args[0] === ADMIN_COMMAND) {
|
|
81
|
-
const port = parseNumericArg(args, '--port') ?? DEFAULT_ADMIN_PORT;
|
|
82
|
-
const configPath = parseStringArg(args, CONFIG_FLAGS);
|
|
83
|
-
const runAdminServer = await loadAdminServer();
|
|
84
|
-
await runAdminServer(process.cwd(), { port, configPath });
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
await runOrchestrator(args, process.cwd());
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
await main();
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { PluginOption } from 'vite';
|
|
2
|
-
/**
|
|
3
|
-
* Vite plugin that intercepts module loading during mutation testing.
|
|
4
|
-
*
|
|
5
|
-
* When MUTATE_FILE and MUTATE_CODE environment variables are set, this plugin
|
|
6
|
-
* will replace the target file's code with the mutated version. This allows
|
|
7
|
-
* vitest to run tests against each mutation variant.
|
|
8
|
-
*
|
|
9
|
-
* Environment Variables:
|
|
10
|
-
* - MUTATE_FILE: Absolute path to the file being mutated
|
|
11
|
-
* - MUTATE_CODE: The mutated source code to inject
|
|
12
|
-
*
|
|
13
|
-
* @returns Vite plugin option
|
|
14
|
-
*/
|
|
15
|
-
export declare function viteMutineerPlugin(): PluginOption;
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
/**
|
|
3
|
-
* Vite plugin that intercepts module loading during mutation testing.
|
|
4
|
-
*
|
|
5
|
-
* When MUTATE_FILE and MUTATE_CODE environment variables are set, this plugin
|
|
6
|
-
* will replace the target file's code with the mutated version. This allows
|
|
7
|
-
* vitest to run tests against each mutation variant.
|
|
8
|
-
*
|
|
9
|
-
* Environment Variables:
|
|
10
|
-
* - MUTATE_FILE: Absolute path to the file being mutated
|
|
11
|
-
* - MUTATE_CODE: The mutated source code to inject
|
|
12
|
-
*
|
|
13
|
-
* @returns Vite plugin option
|
|
14
|
-
*/
|
|
15
|
-
export function viteMutineerPlugin() {
|
|
16
|
-
// Normalize and cache the target path during initialization
|
|
17
|
-
const mutateFile = process.env.MUTATE_FILE;
|
|
18
|
-
const mutateCode = process.env.MUTATE_CODE;
|
|
19
|
-
let normalizedTarget = null;
|
|
20
|
-
if (mutateFile) {
|
|
21
|
-
try {
|
|
22
|
-
normalizedTarget = path.resolve(mutateFile);
|
|
23
|
-
}
|
|
24
|
-
catch (err) {
|
|
25
|
-
console.warn(`[mutineer] Failed to resolve MUTATE_FILE: ${mutateFile}`, err);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
name: 'mutineer:swap',
|
|
30
|
-
enforce: 'pre',
|
|
31
|
-
load(id) {
|
|
32
|
-
// Early exit if no mutation is active
|
|
33
|
-
if (!normalizedTarget || !mutateCode) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
// Normalize the module ID, handling query strings
|
|
37
|
-
const cleanId = id.split('?')[0];
|
|
38
|
-
let normalizedId;
|
|
39
|
-
try {
|
|
40
|
-
normalizedId = path.resolve(cleanId);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
// Return mutated code only if this is the target file
|
|
46
|
-
if (normalizedId === normalizedTarget) {
|
|
47
|
-
return mutateCode;
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vitest setup file that decorates test titles with mutation metadata.
|
|
3
|
-
*
|
|
4
|
-
* When a mutation is active (MUTATE_ACTIVE=1), this file wraps the global
|
|
5
|
-
* test functions (it, test, describe) to append mutation labels to test titles.
|
|
6
|
-
* This helps identify which mutation caused which test failures in the output.
|
|
7
|
-
*
|
|
8
|
-
* Environment Variables:
|
|
9
|
-
* - MUTATE_ACTIVE: Set to '1' to enable mutation decoration
|
|
10
|
-
* - MUTATE_ID: Unique identifier for the mutation (e.g., 'relaxLE#0')
|
|
11
|
-
* - MUTATE_NAME: Human-readable name of the mutation (e.g., 'relaxLE')
|
|
12
|
-
*
|
|
13
|
-
* Example output: "should validate input @relaxLE#0 relaxLE"
|
|
14
|
-
*/
|
|
15
|
-
declare const active: boolean;
|
|
16
|
-
declare const id: string;
|
|
17
|
-
declare const mutationName: string;
|
|
18
|
-
declare const label: string;
|
|
19
|
-
/**
|
|
20
|
-
* Appends mutation label to test title if not already present.
|
|
21
|
-
*/
|
|
22
|
-
declare function decorateTitle(title: unknown): unknown;
|
|
23
|
-
type ItLike = typeof it;
|
|
24
|
-
type DescribeLike = typeof describe;
|
|
25
|
-
type AnyCallable = (...args: any[]) => unknown;
|
|
26
|
-
type Chainers = 'skip' | 'only' | 'todo' | 'concurrent' | 'sequential' | 'fails' | 'retry' | 'runIf' | 'skipIf';
|
|
27
|
-
type ChainableRecord = {
|
|
28
|
-
each?: AnyCallable;
|
|
29
|
-
} & Partial<Record<Chainers, AnyCallable>>;
|
|
30
|
-
type ChainableCallable = AnyCallable & ChainableRecord;
|
|
31
|
-
/** Chainers that need to be forwarded from original to wrapped callable. */
|
|
32
|
-
declare const CHAINERS: readonly Chainers[];
|
|
33
|
-
/**
|
|
34
|
-
* Helper to attach .each(...) method to a callable.
|
|
35
|
-
* Ensures title decoration applies to parameterized tests.
|
|
36
|
-
*/
|
|
37
|
-
declare function attachEachMethod(callable: AnyCallable, each: AnyCallable): AnyCallable;
|
|
38
|
-
/**
|
|
39
|
-
* Wraps a test function (it/test/describe) to decorate titles with mutation metadata.
|
|
40
|
-
* Preserves all chainers, static properties, and symbols from the original function.
|
|
41
|
-
*/
|
|
42
|
-
declare function wrapCallable<T extends AnyCallable>(original: T): T;
|
|
43
|
-
declare const g: typeof globalThis & {
|
|
44
|
-
it?: ItLike;
|
|
45
|
-
test?: ItLike;
|
|
46
|
-
describe?: DescribeLike;
|
|
47
|
-
};
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Vitest setup file that decorates test titles with mutation metadata.
|
|
4
|
-
*
|
|
5
|
-
* When a mutation is active (MUTATE_ACTIVE=1), this file wraps the global
|
|
6
|
-
* test functions (it, test, describe) to append mutation labels to test titles.
|
|
7
|
-
* This helps identify which mutation caused which test failures in the output.
|
|
8
|
-
*
|
|
9
|
-
* Environment Variables:
|
|
10
|
-
* - MUTATE_ACTIVE: Set to '1' to enable mutation decoration
|
|
11
|
-
* - MUTATE_ID: Unique identifier for the mutation (e.g., 'relaxLE#0')
|
|
12
|
-
* - MUTATE_NAME: Human-readable name of the mutation (e.g., 'relaxLE')
|
|
13
|
-
*
|
|
14
|
-
* Example output: "should validate input @relaxLE#0 relaxLE"
|
|
15
|
-
*/
|
|
16
|
-
const active = process.env.MUTATE_ACTIVE === '1';
|
|
17
|
-
// Early exit if mutation is not active
|
|
18
|
-
if (!active) {
|
|
19
|
-
process.exit(0);
|
|
20
|
-
}
|
|
21
|
-
const id = process.env.MUTATE_ID || '';
|
|
22
|
-
const mutationName = process.env.MUTATE_NAME || '';
|
|
23
|
-
const label = `@${id} ${mutationName}`;
|
|
24
|
-
/**
|
|
25
|
-
* Appends mutation label to test title if not already present.
|
|
26
|
-
*/
|
|
27
|
-
function decorateTitle(title) {
|
|
28
|
-
return typeof title === 'string' && !title.includes(label)
|
|
29
|
-
? `${title} ${label}`
|
|
30
|
-
: title;
|
|
31
|
-
}
|
|
32
|
-
/** Chainers that need to be forwarded from original to wrapped callable. */
|
|
33
|
-
const CHAINERS = [
|
|
34
|
-
'skip',
|
|
35
|
-
'only',
|
|
36
|
-
'todo',
|
|
37
|
-
'concurrent',
|
|
38
|
-
'sequential',
|
|
39
|
-
'fails',
|
|
40
|
-
'retry',
|
|
41
|
-
'runIf',
|
|
42
|
-
'skipIf'
|
|
43
|
-
];
|
|
44
|
-
/**
|
|
45
|
-
* Helper to attach .each(...) method to a callable.
|
|
46
|
-
* Ensures title decoration applies to parameterized tests.
|
|
47
|
-
*/
|
|
48
|
-
function attachEachMethod(callable, each) {
|
|
49
|
-
return ((...tableArgs) => {
|
|
50
|
-
const chain = each(...tableArgs);
|
|
51
|
-
return (title, fn, timeout) => chain(decorateTitle(title), fn, timeout);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Wraps a test function (it/test/describe) to decorate titles with mutation metadata.
|
|
56
|
-
* Preserves all chainers, static properties, and symbols from the original function.
|
|
57
|
-
*/
|
|
58
|
-
function wrapCallable(original) {
|
|
59
|
-
// Base wrapper that decorates the title
|
|
60
|
-
const wrapped = ((title, fn, timeout) => {
|
|
61
|
-
return original(decorateTitle(title), fn, timeout);
|
|
62
|
-
});
|
|
63
|
-
const wrappedChainable = wrapped;
|
|
64
|
-
const originalChainable = original;
|
|
65
|
-
// Forward chainers (skip, only, todo, etc.)
|
|
66
|
-
for (const key of CHAINERS) {
|
|
67
|
-
const method = originalChainable[key];
|
|
68
|
-
if (typeof method !== 'function')
|
|
69
|
-
continue;
|
|
70
|
-
const forwarded = ((...args) => method.apply(original, args));
|
|
71
|
-
wrappedChainable[key] = forwarded;
|
|
72
|
-
// Attach .each(...) to chainers that support it
|
|
73
|
-
const each = method.each;
|
|
74
|
-
if (typeof each === 'function') {
|
|
75
|
-
forwarded.each = attachEachMethod(forwarded, each);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
// Attach top-level .each(...)
|
|
79
|
-
const topEach = originalChainable.each;
|
|
80
|
-
if (typeof topEach === 'function') {
|
|
81
|
-
wrappedChainable.each = attachEachMethod(wrapped, topEach);
|
|
82
|
-
}
|
|
83
|
-
// Preserve static properties and symbols from original
|
|
84
|
-
const wrappedRecord = wrapped;
|
|
85
|
-
for (const key of Object.getOwnPropertyNames(original)) {
|
|
86
|
-
if (key in wrappedRecord)
|
|
87
|
-
continue;
|
|
88
|
-
try {
|
|
89
|
-
const desc = Object.getOwnPropertyDescriptor(original, key);
|
|
90
|
-
if (desc)
|
|
91
|
-
Object.defineProperty(wrapped, key, desc);
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// Silently skip properties that cannot be copied
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
for (const sym of Object.getOwnPropertySymbols(original)) {
|
|
98
|
-
if (sym in wrappedRecord)
|
|
99
|
-
continue;
|
|
100
|
-
try {
|
|
101
|
-
const desc = Object.getOwnPropertyDescriptor(original, sym);
|
|
102
|
-
if (desc)
|
|
103
|
-
Object.defineProperty(wrapped, sym, desc);
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
// Silently skip symbols that cannot be copied
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return wrapped;
|
|
110
|
-
}
|
|
111
|
-
// Wrap global test functions
|
|
112
|
-
const g = globalThis;
|
|
113
|
-
if (g.it)
|
|
114
|
-
g.it = wrapCallable(g.it);
|
|
115
|
-
if (g.test)
|
|
116
|
-
g.test = wrapCallable(g.test);
|
|
117
|
-
if (g.describe)
|
|
118
|
-
g.describe = wrapCallable(g.describe);
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { ViteUserConfig } from 'vitest/config';
|
|
2
|
-
/**
|
|
3
|
-
* Wraps a Vite/Vitest configuration to integrate Mutineer mutation testing.
|
|
4
|
-
*
|
|
5
|
-
* When running under mutation testing (MUTATE_ACTIVE=1), this function:
|
|
6
|
-
* - Injects the Mutineer Vite plugin to intercept and replace module code
|
|
7
|
-
* - Enables global test functions (it, test, describe) for convenience
|
|
8
|
-
*
|
|
9
|
-
* @param user - User's Vitest configuration (optional)
|
|
10
|
-
* @returns Enhanced Vitest configuration with Mutineer integration
|
|
11
|
-
* @throws Error if plugins is not an array-like structure
|
|
12
|
-
*/
|
|
13
|
-
export declare function withMutineerSetup(user?: ViteUserConfig): ViteUserConfig;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { viteMutineerPlugin } from './viteMutate.js';
|
|
2
|
-
/** Flag that indicates whether a mutation is currently active. */
|
|
3
|
-
const MUTATION_ACTIVE_FLAG = '1';
|
|
4
|
-
/**
|
|
5
|
-
* Wraps a Vite/Vitest configuration to integrate Mutineer mutation testing.
|
|
6
|
-
*
|
|
7
|
-
* When running under mutation testing (MUTATE_ACTIVE=1), this function:
|
|
8
|
-
* - Injects the Mutineer Vite plugin to intercept and replace module code
|
|
9
|
-
* - Enables global test functions (it, test, describe) for convenience
|
|
10
|
-
*
|
|
11
|
-
* @param user - User's Vitest configuration (optional)
|
|
12
|
-
* @returns Enhanced Vitest configuration with Mutineer integration
|
|
13
|
-
* @throws Error if plugins is not an array-like structure
|
|
14
|
-
*/
|
|
15
|
-
export function withMutineerSetup(user = {}) {
|
|
16
|
-
const isMutating = process.env.MUTATE_ACTIVE === MUTATION_ACTIVE_FLAG;
|
|
17
|
-
// Build plugin list: user plugins + mutineer plugin (if mutating)
|
|
18
|
-
const userPlugins = Array.isArray(user.plugins) ? user.plugins : (user.plugins ? [user.plugins] : []);
|
|
19
|
-
const plugins = isMutating
|
|
20
|
-
? [...userPlugins, viteMutineerPlugin()]
|
|
21
|
-
: userPlugins;
|
|
22
|
-
return {
|
|
23
|
-
...user,
|
|
24
|
-
plugins,
|
|
25
|
-
test: {
|
|
26
|
-
...(user.test ?? {}),
|
|
27
|
-
globals: user.test?.globals ?? true, // default to true for convenience
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
}
|