github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/pr/view/view_test.go (about)

     1  package view
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"os"
    10  	"testing"
    11  
    12  	"github.com/cli/cli/api"
    13  	"github.com/cli/cli/internal/ghrepo"
    14  	"github.com/cli/cli/internal/run"
    15  	"github.com/cli/cli/pkg/cmd/pr/shared"
    16  	"github.com/cli/cli/pkg/cmdutil"
    17  	"github.com/cli/cli/pkg/httpmock"
    18  	"github.com/cli/cli/pkg/iostreams"
    19  	"github.com/cli/cli/test"
    20  	"github.com/google/shlex"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  func Test_NewCmdView(t *testing.T) {
    26  	tests := []struct {
    27  		name    string
    28  		args    string
    29  		isTTY   bool
    30  		want    ViewOptions
    31  		wantErr string
    32  	}{
    33  		{
    34  			name:  "number argument",
    35  			args:  "123",
    36  			isTTY: true,
    37  			want: ViewOptions{
    38  				SelectorArg: "123",
    39  				BrowserMode: false,
    40  			},
    41  		},
    42  		{
    43  			name:  "no argument",
    44  			args:  "",
    45  			isTTY: true,
    46  			want: ViewOptions{
    47  				SelectorArg: "",
    48  				BrowserMode: false,
    49  			},
    50  		},
    51  		{
    52  			name:  "web mode",
    53  			args:  "123 -w",
    54  			isTTY: true,
    55  			want: ViewOptions{
    56  				SelectorArg: "123",
    57  				BrowserMode: true,
    58  			},
    59  		},
    60  		{
    61  			name:    "no argument with --repo override",
    62  			args:    "-R owner/repo",
    63  			isTTY:   true,
    64  			wantErr: "argument required when using the --repo flag",
    65  		},
    66  		{
    67  			name:  "comments",
    68  			args:  "123 -c",
    69  			isTTY: true,
    70  			want: ViewOptions{
    71  				SelectorArg: "123",
    72  				Comments:    true,
    73  			},
    74  		},
    75  	}
    76  	for _, tt := range tests {
    77  		t.Run(tt.name, func(t *testing.T) {
    78  			io, _, _, _ := iostreams.Test()
    79  			io.SetStdoutTTY(tt.isTTY)
    80  			io.SetStdinTTY(tt.isTTY)
    81  			io.SetStderrTTY(tt.isTTY)
    82  
    83  			f := &cmdutil.Factory{
    84  				IOStreams: io,
    85  			}
    86  
    87  			var opts *ViewOptions
    88  			cmd := NewCmdView(f, func(o *ViewOptions) error {
    89  				opts = o
    90  				return nil
    91  			})
    92  			cmd.PersistentFlags().StringP("repo", "R", "", "")
    93  
    94  			argv, err := shlex.Split(tt.args)
    95  			require.NoError(t, err)
    96  			cmd.SetArgs(argv)
    97  
    98  			cmd.SetIn(&bytes.Buffer{})
    99  			cmd.SetOut(ioutil.Discard)
   100  			cmd.SetErr(ioutil.Discard)
   101  
   102  			_, err = cmd.ExecuteC()
   103  			if tt.wantErr != "" {
   104  				require.EqualError(t, err, tt.wantErr)
   105  				return
   106  			} else {
   107  				require.NoError(t, err)
   108  			}
   109  
   110  			assert.Equal(t, tt.want.SelectorArg, opts.SelectorArg)
   111  		})
   112  	}
   113  }
   114  
   115  func runCommand(rt http.RoundTripper, branch string, isTTY bool, cli string) (*test.CmdOut, error) {
   116  	io, _, stdout, stderr := iostreams.Test()
   117  	io.SetStdoutTTY(isTTY)
   118  	io.SetStdinTTY(isTTY)
   119  	io.SetStderrTTY(isTTY)
   120  
   121  	browser := &cmdutil.TestBrowser{}
   122  	factory := &cmdutil.Factory{
   123  		IOStreams: io,
   124  		Browser:   browser,
   125  	}
   126  
   127  	cmd := NewCmdView(factory, nil)
   128  
   129  	argv, err := shlex.Split(cli)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	cmd.SetArgs(argv)
   134  
   135  	cmd.SetIn(&bytes.Buffer{})
   136  	cmd.SetOut(ioutil.Discard)
   137  	cmd.SetErr(ioutil.Discard)
   138  
   139  	_, err = cmd.ExecuteC()
   140  	return &test.CmdOut{
   141  		OutBuf:     stdout,
   142  		ErrBuf:     stderr,
   143  		BrowsedURL: browser.BrowsedURL(),
   144  	}, err
   145  }
   146  
   147  // hack for compatibility with old JSON fixture files
   148  func prFromFixtures(fixtures map[string]string) (*api.PullRequest, error) {
   149  	var response struct {
   150  		Data struct {
   151  			Repository struct {
   152  				PullRequest *api.PullRequest
   153  			}
   154  		}
   155  	}
   156  
   157  	ff, err := os.Open(fixtures["PullRequestByNumber"])
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	defer ff.Close()
   162  
   163  	dec := json.NewDecoder(ff)
   164  	err = dec.Decode(&response)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	for name := range fixtures {
   170  		switch name {
   171  		case "PullRequestByNumber":
   172  		case "ReviewsForPullRequest", "CommentsForPullRequest":
   173  			ff, err := os.Open(fixtures[name])
   174  			if err != nil {
   175  				return nil, err
   176  			}
   177  			defer ff.Close()
   178  			dec := json.NewDecoder(ff)
   179  			err = dec.Decode(&response)
   180  			if err != nil {
   181  				return nil, err
   182  			}
   183  		default:
   184  			return nil, fmt.Errorf("unrecognized fixture type: %q", name)
   185  		}
   186  	}
   187  
   188  	return response.Data.Repository.PullRequest, nil
   189  }
   190  
   191  func TestPRView_Preview_nontty(t *testing.T) {
   192  	tests := map[string]struct {
   193  		branch          string
   194  		args            string
   195  		fixtures        map[string]string
   196  		expectedOutputs []string
   197  	}{
   198  		"Open PR without metadata": {
   199  			branch: "master",
   200  			args:   "12",
   201  			fixtures: map[string]string{
   202  				"PullRequestByNumber": "./fixtures/prViewPreview.json",
   203  			},
   204  			expectedOutputs: []string{
   205  				`title:\tBlueberries are from a fork\n`,
   206  				`state:\tOPEN\n`,
   207  				`author:\tnobody\n`,
   208  				`labels:\t\n`,
   209  				`assignees:\t\n`,
   210  				`reviewers:\t\n`,
   211  				`projects:\t\n`,
   212  				`milestone:\t\n`,
   213  				`url:\thttps://github.com/OWNER/REPO/pull/12\n`,
   214  				`additions:\t100\n`,
   215  				`deletions:\t10\n`,
   216  				`number:\t12\n`,
   217  				`blueberries taste good`,
   218  			},
   219  		},
   220  		"Open PR with metadata by number": {
   221  			branch: "master",
   222  			args:   "12",
   223  			fixtures: map[string]string{
   224  				"PullRequestByNumber": "./fixtures/prViewPreviewWithMetadataByNumber.json",
   225  			},
   226  			expectedOutputs: []string{
   227  				`title:\tBlueberries are from a fork\n`,
   228  				`reviewers:\t1 \(Requested\)\n`,
   229  				`assignees:\tmarseilles, monaco\n`,
   230  				`labels:\tone, two, three, four, five\n`,
   231  				`projects:\tProject 1 \(column A\), Project 2 \(column B\), Project 3 \(column C\), Project 4 \(Awaiting triage\)\n`,
   232  				`milestone:\tuluru\n`,
   233  				`\*\*blueberries taste good\*\*`,
   234  			},
   235  		},
   236  		"Open PR with reviewers by number": {
   237  			branch: "master",
   238  			args:   "12",
   239  			fixtures: map[string]string{
   240  				"PullRequestByNumber":   "./fixtures/prViewPreviewWithReviewersByNumber.json",
   241  				"ReviewsForPullRequest": "./fixtures/prViewPreviewManyReviews.json",
   242  			},
   243  			expectedOutputs: []string{
   244  				`title:\tBlueberries are from a fork\n`,
   245  				`state:\tOPEN\n`,
   246  				`author:\tnobody\n`,
   247  				`labels:\t\n`,
   248  				`assignees:\t\n`,
   249  				`projects:\t\n`,
   250  				`milestone:\t\n`,
   251  				`additions:\t100\n`,
   252  				`deletions:\t10\n`,
   253  				`reviewers:\tDEF \(Commented\), def \(Changes requested\), ghost \(Approved\), hubot \(Commented\), xyz \(Approved\), 123 \(Requested\), abc \(Requested\), my-org\/team-1 \(Requested\)\n`,
   254  				`\*\*blueberries taste good\*\*`,
   255  			},
   256  		},
   257  		"Closed PR": {
   258  			branch: "master",
   259  			args:   "12",
   260  			fixtures: map[string]string{
   261  				"PullRequestByNumber": "./fixtures/prViewPreviewClosedState.json",
   262  			},
   263  			expectedOutputs: []string{
   264  				`state:\tCLOSED\n`,
   265  				`author:\tnobody\n`,
   266  				`labels:\t\n`,
   267  				`assignees:\t\n`,
   268  				`reviewers:\t\n`,
   269  				`projects:\t\n`,
   270  				`milestone:\t\n`,
   271  				`additions:\t100\n`,
   272  				`deletions:\t10\n`,
   273  				`\*\*blueberries taste good\*\*`,
   274  			},
   275  		},
   276  		"Merged PR": {
   277  			branch: "master",
   278  			args:   "12",
   279  			fixtures: map[string]string{
   280  				"PullRequestByNumber": "./fixtures/prViewPreviewMergedState.json",
   281  			},
   282  			expectedOutputs: []string{
   283  				`state:\tMERGED\n`,
   284  				`author:\tnobody\n`,
   285  				`labels:\t\n`,
   286  				`assignees:\t\n`,
   287  				`reviewers:\t\n`,
   288  				`projects:\t\n`,
   289  				`milestone:\t\n`,
   290  				`additions:\t100\n`,
   291  				`deletions:\t10\n`,
   292  				`\*\*blueberries taste good\*\*`,
   293  			},
   294  		},
   295  		"Draft PR": {
   296  			branch: "master",
   297  			args:   "12",
   298  			fixtures: map[string]string{
   299  				"PullRequestByNumber": "./fixtures/prViewPreviewDraftState.json",
   300  			},
   301  			expectedOutputs: []string{
   302  				`title:\tBlueberries are from a fork\n`,
   303  				`state:\tDRAFT\n`,
   304  				`author:\tnobody\n`,
   305  				`labels:`,
   306  				`assignees:`,
   307  				`reviewers:`,
   308  				`projects:`,
   309  				`milestone:`,
   310  				`additions:\t100\n`,
   311  				`deletions:\t10\n`,
   312  				`\*\*blueberries taste good\*\*`,
   313  			},
   314  		},
   315  	}
   316  
   317  	for name, tc := range tests {
   318  		t.Run(name, func(t *testing.T) {
   319  			http := &httpmock.Registry{}
   320  			defer http.Verify(t)
   321  
   322  			pr, err := prFromFixtures(tc.fixtures)
   323  			require.NoError(t, err)
   324  			shared.RunCommandFinder("12", pr, ghrepo.New("OWNER", "REPO"))
   325  
   326  			output, err := runCommand(http, tc.branch, false, tc.args)
   327  			if err != nil {
   328  				t.Errorf("error running command `%v`: %v", tc.args, err)
   329  			}
   330  
   331  			assert.Equal(t, "", output.Stderr())
   332  
   333  			//nolint:staticcheck // prefer exact matchers over ExpectLines
   334  			test.ExpectLines(t, output.String(), tc.expectedOutputs...)
   335  		})
   336  	}
   337  }
   338  
   339  func TestPRView_Preview(t *testing.T) {
   340  	tests := map[string]struct {
   341  		branch          string
   342  		args            string
   343  		fixtures        map[string]string
   344  		expectedOutputs []string
   345  	}{
   346  		"Open PR without metadata": {
   347  			branch: "master",
   348  			args:   "12",
   349  			fixtures: map[string]string{
   350  				"PullRequestByNumber": "./fixtures/prViewPreview.json",
   351  			},
   352  			expectedOutputs: []string{
   353  				`Blueberries are from a fork #12`,
   354  				`Open.*nobody wants to merge 12 commits into master from blueberries.+100.-10`,
   355  				`blueberries taste good`,
   356  				`View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`,
   357  			},
   358  		},
   359  		"Open PR with metadata by number": {
   360  			branch: "master",
   361  			args:   "12",
   362  			fixtures: map[string]string{
   363  				"PullRequestByNumber": "./fixtures/prViewPreviewWithMetadataByNumber.json",
   364  			},
   365  			expectedOutputs: []string{
   366  				`Blueberries are from a fork #12`,
   367  				`Open.*nobody wants to merge 12 commits into master from blueberries.+100.-10`,
   368  				`Reviewers:.*1 \(.*Requested.*\)\n`,
   369  				`Assignees:.*marseilles, monaco\n`,
   370  				`Labels:.*one, two, three, four, five\n`,
   371  				`Projects:.*Project 1 \(column A\), Project 2 \(column B\), Project 3 \(column C\), Project 4 \(Awaiting triage\)\n`,
   372  				`Milestone:.*uluru\n`,
   373  				`blueberries taste good`,
   374  				`View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`,
   375  			},
   376  		},
   377  		"Open PR with reviewers by number": {
   378  			branch: "master",
   379  			args:   "12",
   380  			fixtures: map[string]string{
   381  				"PullRequestByNumber":   "./fixtures/prViewPreviewWithReviewersByNumber.json",
   382  				"ReviewsForPullRequest": "./fixtures/prViewPreviewManyReviews.json",
   383  			},
   384  			expectedOutputs: []string{
   385  				`Blueberries are from a fork #12`,
   386  				`Reviewers: DEF \(Commented\), def \(Changes requested\), ghost \(Approved\), hubot \(Commented\), xyz \(Approved\), 123 \(Requested\), abc \(Requested\), my-org\/team-1 \(Requested\)`,
   387  				`blueberries taste good`,
   388  				`View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`,
   389  			},
   390  		},
   391  		"Closed PR": {
   392  			branch: "master",
   393  			args:   "12",
   394  			fixtures: map[string]string{
   395  				"PullRequestByNumber": "./fixtures/prViewPreviewClosedState.json",
   396  			},
   397  			expectedOutputs: []string{
   398  				`Blueberries are from a fork #12`,
   399  				`Closed.*nobody wants to merge 12 commits into master from blueberries.+100.-10`,
   400  				`blueberries taste good`,
   401  				`View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`,
   402  			},
   403  		},
   404  		"Merged PR": {
   405  			branch: "master",
   406  			args:   "12",
   407  			fixtures: map[string]string{
   408  				"PullRequestByNumber": "./fixtures/prViewPreviewMergedState.json",
   409  			},
   410  			expectedOutputs: []string{
   411  				`Blueberries are from a fork #12`,
   412  				`Merged.*nobody wants to merge 12 commits into master from blueberries.+100.-10`,
   413  				`blueberries taste good`,
   414  				`View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`,
   415  			},
   416  		},
   417  		"Draft PR": {
   418  			branch: "master",
   419  			args:   "12",
   420  			fixtures: map[string]string{
   421  				"PullRequestByNumber": "./fixtures/prViewPreviewDraftState.json",
   422  			},
   423  			expectedOutputs: []string{
   424  				`Blueberries are from a fork #12`,
   425  				`Draft.*nobody wants to merge 12 commits into master from blueberries.+100.-10`,
   426  				`blueberries taste good`,
   427  				`View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`,
   428  			},
   429  		},
   430  	}
   431  
   432  	for name, tc := range tests {
   433  		t.Run(name, func(t *testing.T) {
   434  			http := &httpmock.Registry{}
   435  			defer http.Verify(t)
   436  
   437  			pr, err := prFromFixtures(tc.fixtures)
   438  			require.NoError(t, err)
   439  			shared.RunCommandFinder("12", pr, ghrepo.New("OWNER", "REPO"))
   440  
   441  			output, err := runCommand(http, tc.branch, true, tc.args)
   442  			if err != nil {
   443  				t.Errorf("error running command `%v`: %v", tc.args, err)
   444  			}
   445  
   446  			assert.Equal(t, "", output.Stderr())
   447  
   448  			//nolint:staticcheck // prefer exact matchers over ExpectLines
   449  			test.ExpectLines(t, output.String(), tc.expectedOutputs...)
   450  		})
   451  	}
   452  }
   453  
   454  func TestPRView_web_currentBranch(t *testing.T) {
   455  	http := &httpmock.Registry{}
   456  	defer http.Verify(t)
   457  
   458  	shared.RunCommandFinder("", &api.PullRequest{URL: "https://github.com/OWNER/REPO/pull/10"}, ghrepo.New("OWNER", "REPO"))
   459  
   460  	_, cmdTeardown := run.Stub()
   461  	defer cmdTeardown(t)
   462  
   463  	output, err := runCommand(http, "blueberries", true, "-w")
   464  	if err != nil {
   465  		t.Errorf("error running command `pr view`: %v", err)
   466  	}
   467  
   468  	assert.Equal(t, "", output.String())
   469  	assert.Equal(t, "Opening github.com/OWNER/REPO/pull/10 in your browser.\n", output.Stderr())
   470  	assert.Equal(t, "https://github.com/OWNER/REPO/pull/10", output.BrowsedURL)
   471  }
   472  
   473  func TestPRView_web_noResultsForBranch(t *testing.T) {
   474  	http := &httpmock.Registry{}
   475  	defer http.Verify(t)
   476  
   477  	shared.RunCommandFinder("", nil, nil)
   478  
   479  	_, cmdTeardown := run.Stub()
   480  	defer cmdTeardown(t)
   481  
   482  	_, err := runCommand(http, "blueberries", true, "-w")
   483  	if err == nil || err.Error() != `no pull requests found` {
   484  		t.Errorf("error running command `pr view`: %v", err)
   485  	}
   486  }
   487  
   488  func TestPRView_tty_Comments(t *testing.T) {
   489  	tests := map[string]struct {
   490  		branch          string
   491  		cli             string
   492  		fixtures        map[string]string
   493  		expectedOutputs []string
   494  		wantsErr        bool
   495  	}{
   496  		"without comments flag": {
   497  			branch: "master",
   498  			cli:    "123",
   499  			fixtures: map[string]string{
   500  				"PullRequestByNumber":   "./fixtures/prViewPreviewSingleComment.json",
   501  				"ReviewsForPullRequest": "./fixtures/prViewPreviewReviews.json",
   502  			},
   503  			expectedOutputs: []string{
   504  				`some title #12`,
   505  				`1 \x{1f615} • 2 \x{1f440} • 3 \x{2764}\x{fe0f}`,
   506  				`some body`,
   507  				`———————— Not showing 9 comments ————————`,
   508  				`marseilles \(Collaborator\) • Jan  9, 2020 • Newest comment`,
   509  				`4 \x{1f389} • 5 \x{1f604} • 6 \x{1f680}`,
   510  				`Comment 5`,
   511  				`Use --comments to view the full conversation`,
   512  				`View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`,
   513  			},
   514  		},
   515  		"with comments flag": {
   516  			branch: "master",
   517  			cli:    "123 --comments",
   518  			fixtures: map[string]string{
   519  				"PullRequestByNumber":    "./fixtures/prViewPreviewSingleComment.json",
   520  				"ReviewsForPullRequest":  "./fixtures/prViewPreviewReviews.json",
   521  				"CommentsForPullRequest": "./fixtures/prViewPreviewFullComments.json",
   522  			},
   523  			expectedOutputs: []string{
   524  				`some title #12`,
   525  				`some body`,
   526  				`monalisa • Jan  1, 2020 • Edited`,
   527  				`1 \x{1f615} • 2 \x{1f440} • 3 \x{2764}\x{fe0f} • 4 \x{1f389} • 5 \x{1f604} • 6 \x{1f680} • 7 \x{1f44e} • 8 \x{1f44d}`,
   528  				`Comment 1`,
   529  				`sam commented • Jan  2, 2020`,
   530  				`1 \x{1f44e} • 1 \x{1f44d}`,
   531  				`Review 1`,
   532  				`View the full review: https://github.com/OWNER/REPO/pull/12#pullrequestreview-1`,
   533  				`johnnytest \(Contributor\) • Jan  3, 2020`,
   534  				`Comment 2`,
   535  				`matt requested changes \(Owner\) • Jan  4, 2020`,
   536  				`1 \x{1f615} • 1 \x{1f440}`,
   537  				`Review 2`,
   538  				`View the full review: https://github.com/OWNER/REPO/pull/12#pullrequestreview-2`,
   539  				`elvisp \(Member\) • Jan  5, 2020`,
   540  				`Comment 3`,
   541  				`leah approved \(Member\) • Jan  6, 2020 • Edited`,
   542  				`Review 3`,
   543  				`View the full review: https://github.com/OWNER/REPO/pull/12#pullrequestreview-3`,
   544  				`loislane \(Owner\) • Jan  7, 2020`,
   545  				`Comment 4`,
   546  				`louise dismissed • Jan  8, 2020`,
   547  				`Review 4`,
   548  				`View the full review: https://github.com/OWNER/REPO/pull/12#pullrequestreview-4`,
   549  				`sam-spam • This comment has been marked as spam`,
   550  				`marseilles \(Collaborator\) • Jan  9, 2020 • Newest comment`,
   551  				`Comment 5`,
   552  				`View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`,
   553  			},
   554  		},
   555  		"with invalid comments flag": {
   556  			branch:   "master",
   557  			cli:      "123 --comments 3",
   558  			wantsErr: true,
   559  		},
   560  	}
   561  	for name, tt := range tests {
   562  		t.Run(name, func(t *testing.T) {
   563  			http := &httpmock.Registry{}
   564  			defer http.Verify(t)
   565  
   566  			if len(tt.fixtures) > 0 {
   567  				pr, err := prFromFixtures(tt.fixtures)
   568  				require.NoError(t, err)
   569  				shared.RunCommandFinder("123", pr, ghrepo.New("OWNER", "REPO"))
   570  			} else {
   571  				shared.RunCommandFinder("123", nil, nil)
   572  			}
   573  
   574  			output, err := runCommand(http, tt.branch, true, tt.cli)
   575  			if tt.wantsErr {
   576  				assert.Error(t, err)
   577  				return
   578  			}
   579  			assert.NoError(t, err)
   580  			assert.Equal(t, "", output.Stderr())
   581  			//nolint:staticcheck // prefer exact matchers over ExpectLines
   582  			test.ExpectLines(t, output.String(), tt.expectedOutputs...)
   583  		})
   584  	}
   585  }
   586  
   587  func TestPRView_nontty_Comments(t *testing.T) {
   588  	tests := map[string]struct {
   589  		branch          string
   590  		cli             string
   591  		fixtures        map[string]string
   592  		expectedOutputs []string
   593  		wantsErr        bool
   594  	}{
   595  		"without comments flag": {
   596  			branch: "master",
   597  			cli:    "123",
   598  			fixtures: map[string]string{
   599  				"PullRequestByNumber":   "./fixtures/prViewPreviewSingleComment.json",
   600  				"ReviewsForPullRequest": "./fixtures/prViewPreviewReviews.json",
   601  			},
   602  			expectedOutputs: []string{
   603  				`title:\tsome title`,
   604  				`state:\tOPEN`,
   605  				`author:\tnobody`,
   606  				`url:\thttps://github.com/OWNER/REPO/pull/12`,
   607  				`some body`,
   608  			},
   609  		},
   610  		"with comments flag": {
   611  			branch: "master",
   612  			cli:    "123 --comments",
   613  			fixtures: map[string]string{
   614  				"PullRequestByNumber":    "./fixtures/prViewPreviewSingleComment.json",
   615  				"ReviewsForPullRequest":  "./fixtures/prViewPreviewReviews.json",
   616  				"CommentsForPullRequest": "./fixtures/prViewPreviewFullComments.json",
   617  			},
   618  			expectedOutputs: []string{
   619  				`author:\tmonalisa`,
   620  				`association:\tnone`,
   621  				`edited:\ttrue`,
   622  				`status:\tnone`,
   623  				`Comment 1`,
   624  				`author:\tsam`,
   625  				`association:\tnone`,
   626  				`edited:\tfalse`,
   627  				`status:\tcommented`,
   628  				`Review 1`,
   629  				`author:\tjohnnytest`,
   630  				`association:\tcontributor`,
   631  				`edited:\tfalse`,
   632  				`status:\tnone`,
   633  				`Comment 2`,
   634  				`author:\tmatt`,
   635  				`association:\towner`,
   636  				`edited:\tfalse`,
   637  				`status:\tchanges requested`,
   638  				`Review 2`,
   639  				`author:\telvisp`,
   640  				`association:\tmember`,
   641  				`edited:\tfalse`,
   642  				`status:\tnone`,
   643  				`Comment 3`,
   644  				`author:\tleah`,
   645  				`association:\tmember`,
   646  				`edited:\ttrue`,
   647  				`status:\tapproved`,
   648  				`Review 3`,
   649  				`author:\tloislane`,
   650  				`association:\towner`,
   651  				`edited:\tfalse`,
   652  				`status:\tnone`,
   653  				`Comment 4`,
   654  				`author:\tlouise`,
   655  				`association:\tnone`,
   656  				`edited:\tfalse`,
   657  				`status:\tdismissed`,
   658  				`Review 4`,
   659  				`author:\tmarseilles`,
   660  				`association:\tcollaborator`,
   661  				`edited:\tfalse`,
   662  				`status:\tnone`,
   663  				`Comment 5`,
   664  			},
   665  		},
   666  		"with invalid comments flag": {
   667  			branch:   "master",
   668  			cli:      "123 --comments 3",
   669  			wantsErr: true,
   670  		},
   671  	}
   672  	for name, tt := range tests {
   673  		t.Run(name, func(t *testing.T) {
   674  			http := &httpmock.Registry{}
   675  			defer http.Verify(t)
   676  
   677  			if len(tt.fixtures) > 0 {
   678  				pr, err := prFromFixtures(tt.fixtures)
   679  				require.NoError(t, err)
   680  				shared.RunCommandFinder("123", pr, ghrepo.New("OWNER", "REPO"))
   681  			} else {
   682  				shared.RunCommandFinder("123", nil, nil)
   683  			}
   684  
   685  			output, err := runCommand(http, tt.branch, false, tt.cli)
   686  			if tt.wantsErr {
   687  				assert.Error(t, err)
   688  				return
   689  			}
   690  			assert.NoError(t, err)
   691  			assert.Equal(t, "", output.Stderr())
   692  			//nolint:staticcheck // prefer exact matchers over ExpectLines
   693  			test.ExpectLines(t, output.String(), tt.expectedOutputs...)
   694  		})
   695  	}
   696  }