@@ -36,10 +36,14 @@ import * as inter from "../../../src/lib/init/interactive.js";
3636// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference
3737import * as ops from "../../../src/lib/init/local-ops.js" ;
3838import type {
39+ LocalOpPayload ,
3940 WizardOptions ,
4041 WorkflowRunResult ,
4142} from "../../../src/lib/init/types.js" ;
42- import { runWizard } from "../../../src/lib/init/wizard-runner.js" ;
43+ import {
44+ describeLocalOp ,
45+ runWizard ,
46+ } from "../../../src/lib/init/wizard-runner.js" ;
4347// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference
4448import * as sentryUrls from "../../../src/lib/sentry-urls.js" ;
4549
@@ -538,7 +542,7 @@ describe("runWizard", () => {
538542 expect ( payload . operation ) . toBe ( "list-dir" ) ;
539543 } ) ;
540544
541- test ( "uses detail field for spinner message when present " , async ( ) => {
545+ test ( "ignores server detail and generates message from payload params " , async ( ) => {
542546 mockStartResult = {
543547 status : "suspended" ,
544548 suspended : [ [ "install-deps" ] ] ,
@@ -547,10 +551,10 @@ describe("runWizard", () => {
547551 suspendPayload : {
548552 type : "local-op" ,
549553 operation : "run-commands" ,
550- detail : "npm install @sentry/nextjs @sentry/profiling-node " ,
554+ detail : "Server-provided message about next.config.js " ,
551555 cwd : "/app" ,
552556 params : {
553- commands : [ "npm install @ sentry/nextjs @sentry/profiling-node " ] ,
557+ commands : [ "pip install sentry-sdk " ] ,
554558 } ,
555559 } ,
556560 } ,
@@ -560,12 +564,13 @@ describe("runWizard", () => {
560564
561565 await runWizard ( makeOptions ( ) ) ;
562566
567+ // Should use CLI-generated message, NOT the server detail
563568 expect ( spinnerMock . message ) . toHaveBeenCalledWith (
564- "npm install @ sentry/nextjs @sentry/profiling-node "
569+ "Running pip install sentry-sdk... "
565570 ) ;
566571 } ) ;
567572
568- test ( "falls back to generic message when detail is absent" , async ( ) => {
573+ test ( "generates message from payload when detail is absent" , async ( ) => {
569574 mockStartResult = {
570575 status : "suspended" ,
571576 suspended : [ [ "install-deps" ] ] ,
@@ -585,19 +590,17 @@ describe("runWizard", () => {
585590 await runWizard ( makeOptions ( ) ) ;
586591
587592 expect ( spinnerMock . message ) . toHaveBeenCalledWith (
588- "Installing dependencies (run-commands) ..."
593+ "Running npm install @sentry/nextjs ..."
589594 ) ;
590595 } ) ;
591596
592- test ( "truncates detail message when terminal is narrow" , async ( ) => {
597+ test ( "truncates generated message when terminal is narrow" , async ( ) => {
593598 const origColumns = process . stdout . columns ;
594599 Object . defineProperty ( process . stdout , "columns" , {
595600 value : 40 ,
596601 configurable : true ,
597602 } ) ;
598603
599- const longDetail =
600- "npm install @sentry/nextjs @sentry/profiling-node @sentry/browser" ;
601604 mockStartResult = {
602605 status : "suspended" ,
603606 suspended : [ [ "install-deps" ] ] ,
@@ -606,9 +609,12 @@ describe("runWizard", () => {
606609 suspendPayload : {
607610 type : "local-op" ,
608611 operation : "run-commands" ,
609- detail : longDetail ,
610612 cwd : "/app" ,
611- params : { commands : [ longDetail ] } ,
613+ params : {
614+ commands : [
615+ "npm install @sentry/nextjs @sentry/profiling-node @sentry/browser" ,
616+ ] ,
617+ } ,
612618 } ,
613619 } ,
614620 } ,
@@ -620,7 +626,7 @@ describe("runWizard", () => {
620626
621627 // 40 columns - 4 reserved = 36 max, truncated with "…"
622628 const call = spinnerMock . message . mock . calls . find ( ( c : string [ ] ) =>
623- c [ 0 ] ?. includes ( "npm install " )
629+ c [ 0 ] ?. includes ( "Running " )
624630 ) as string [ ] | undefined ;
625631 expect ( call ) . toBeDefined ( ) ;
626632 const msg = call ?. [ 0 ] ?? "" ;
@@ -887,3 +893,176 @@ describe("runWizard", () => {
887893 } ) ;
888894 } ) ;
889895} ) ;
896+
897+ // ── describeLocalOp unit tests ──────────────────────────────────────────────
898+
899+ describe ( "describeLocalOp" , ( ) => {
900+ function payload (
901+ overrides : Partial < LocalOpPayload > &
902+ Pick < LocalOpPayload , "operation" | "params" >
903+ ) : LocalOpPayload {
904+ return { type : "local-op" , cwd : "/app" , ...overrides } as LocalOpPayload ;
905+ }
906+
907+ describe ( "read-files" , ( ) => {
908+ test ( "single file shows basename" , ( ) => {
909+ const msg = describeLocalOp (
910+ payload ( {
911+ operation : "read-files" ,
912+ params : { paths : [ "src/settings.py" ] } ,
913+ } )
914+ ) ;
915+ expect ( msg ) . toBe ( "Reading settings.py..." ) ;
916+ } ) ;
917+
918+ test ( "two files shows both basenames" , ( ) => {
919+ const msg = describeLocalOp (
920+ payload ( {
921+ operation : "read-files" ,
922+ params : { paths : [ "src/settings.py" , "src/urls.py" ] } ,
923+ } )
924+ ) ;
925+ expect ( msg ) . toBe ( "Reading settings.py, urls.py..." ) ;
926+ } ) ;
927+
928+ test ( "three+ files shows count and first two basenames" , ( ) => {
929+ const msg = describeLocalOp (
930+ payload ( {
931+ operation : "read-files" ,
932+ params : {
933+ paths : [ "a/one.py" , "b/two.py" , "c/three.py" , "d/four.py" ] ,
934+ } ,
935+ } )
936+ ) ;
937+ expect ( msg ) . toBe ( "Reading 4 files (one.py, two.py, ...)..." ) ;
938+ } ) ;
939+
940+ test ( "empty paths array" , ( ) => {
941+ const msg = describeLocalOp (
942+ payload ( { operation : "read-files" , params : { paths : [ ] } } )
943+ ) ;
944+ expect ( msg ) . toBe ( "Reading files..." ) ;
945+ } ) ;
946+ } ) ;
947+
948+ describe ( "file-exists-batch" , ( ) => {
949+ test ( "single file shows basename" , ( ) => {
950+ const msg = describeLocalOp (
951+ payload ( {
952+ operation : "file-exists-batch" ,
953+ params : { paths : [ "requirements.txt" ] } ,
954+ } )
955+ ) ;
956+ expect ( msg ) . toBe ( "Checking requirements.txt..." ) ;
957+ } ) ;
958+
959+ test ( "multiple files shows count" , ( ) => {
960+ const msg = describeLocalOp (
961+ payload ( {
962+ operation : "file-exists-batch" ,
963+ params : { paths : [ "a.py" , "b.py" , "c.py" ] } ,
964+ } )
965+ ) ;
966+ expect ( msg ) . toBe ( "Checking 3 files (a.py, b.py, ...)..." ) ;
967+ } ) ;
968+ } ) ;
969+
970+ describe ( "apply-patchset" , ( ) => {
971+ test ( "single create shows verb and basename" , ( ) => {
972+ const msg = describeLocalOp (
973+ payload ( {
974+ operation : "apply-patchset" ,
975+ params : {
976+ patches : [ { path : "src/sentry.py" , action : "create" , patch : "" } ] ,
977+ } ,
978+ } )
979+ ) ;
980+ expect ( msg ) . toBe ( "Creating sentry.py..." ) ;
981+ } ) ;
982+
983+ test ( "single modify shows verb and basename" , ( ) => {
984+ const msg = describeLocalOp (
985+ payload ( {
986+ operation : "apply-patchset" ,
987+ params : {
988+ patches : [ { path : "settings.py" , action : "modify" , patch : "" } ] ,
989+ } ,
990+ } )
991+ ) ;
992+ expect ( msg ) . toBe ( "Modifying settings.py..." ) ;
993+ } ) ;
994+
995+ test ( "single delete shows verb and basename" , ( ) => {
996+ const msg = describeLocalOp (
997+ payload ( {
998+ operation : "apply-patchset" ,
999+ params : {
1000+ patches : [ { path : "old.js" , action : "delete" , patch : "" } ] ,
1001+ } ,
1002+ } )
1003+ ) ;
1004+ expect ( msg ) . toBe ( "Deleting old.js..." ) ;
1005+ } ) ;
1006+
1007+ test ( "multiple patches shows count and breakdown" , ( ) => {
1008+ const msg = describeLocalOp (
1009+ payload ( {
1010+ operation : "apply-patchset" ,
1011+ params : {
1012+ patches : [
1013+ { path : "a.py" , action : "create" , patch : "" } ,
1014+ { path : "b.py" , action : "create" , patch : "" } ,
1015+ { path : "c.py" , action : "modify" , patch : "" } ,
1016+ ] ,
1017+ } ,
1018+ } )
1019+ ) ;
1020+ expect ( msg ) . toBe ( "Applying 3 file changes (2 created, 1 modified)..." ) ;
1021+ } ) ;
1022+ } ) ;
1023+
1024+ describe ( "run-commands" , ( ) => {
1025+ test ( "single command shows the command" , ( ) => {
1026+ const msg = describeLocalOp (
1027+ payload ( {
1028+ operation : "run-commands" ,
1029+ params : { commands : [ "pip install sentry-sdk" ] } ,
1030+ } )
1031+ ) ;
1032+ expect ( msg ) . toBe ( "Running pip install sentry-sdk..." ) ;
1033+ } ) ;
1034+
1035+ test ( "multiple commands shows count and first" , ( ) => {
1036+ const msg = describeLocalOp (
1037+ payload ( {
1038+ operation : "run-commands" ,
1039+ params : {
1040+ commands : [ "pip install sentry-sdk" , "python manage.py check" ] ,
1041+ } ,
1042+ } )
1043+ ) ;
1044+ expect ( msg ) . toBe ( "Running 2 commands (pip install sentry-sdk, ...)..." ) ;
1045+ } ) ;
1046+ } ) ;
1047+
1048+ describe ( "list-dir" , ( ) => {
1049+ test ( "shows generic listing message" , ( ) => {
1050+ const msg = describeLocalOp (
1051+ payload ( { operation : "list-dir" , params : { path : "." } } )
1052+ ) ;
1053+ expect ( msg ) . toBe ( "Listing directory..." ) ;
1054+ } ) ;
1055+ } ) ;
1056+
1057+ describe ( "create-sentry-project" , ( ) => {
1058+ test ( "shows project name and platform" , ( ) => {
1059+ const msg = describeLocalOp (
1060+ payload ( {
1061+ operation : "create-sentry-project" ,
1062+ params : { name : "my-app" , platform : "python-django" } ,
1063+ } )
1064+ ) ;
1065+ expect ( msg ) . toBe ( 'Creating project "my-app" (python-django)...' ) ;
1066+ } ) ;
1067+ } ) ;
1068+ } ) ;
0 commit comments