github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/pr/view/view_test.go (about)

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