Add PR reviews and comments to GitHub api (#179)

Also, a slight re-organization of the GitHub api test code.
This commit is contained in:
Dandelion Mané 2018-04-30 18:22:03 -07:00 committed by GitHub
parent 16e8e399e6
commit a1d072846d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 142 additions and 66 deletions

View File

@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GitHub porcelain API Author 1`] = `2`; exports[`GitHub porcelain API has wrappers for Authors 1`] = `2`;
exports[`GitHub porcelain API Author 2`] = ` exports[`GitHub porcelain API has wrappers for Authors 2`] = `
Object { Object {
"address": Object { "address": Object {
"id": "https://github.com/decentralion", "id": "https://github.com/decentralion",
@ -18,9 +18,9 @@ Object {
} }
`; `;
exports[`GitHub porcelain API Comment 1`] = `3`; exports[`GitHub porcelain API has wrappers for Comments 1`] = `3`;
exports[`GitHub porcelain API Comment 2`] = ` exports[`GitHub porcelain API has wrappers for Comments 2`] = `
Object { Object {
"address": Object { "address": Object {
"id": "https://github.com/sourcecred/example-repo/issues/6#issuecomment-373768442", "id": "https://github.com/sourcecred/example-repo/issues/6#issuecomment-373768442",
@ -35,7 +35,7 @@ Object {
} }
`; `;
exports[`GitHub porcelain API Issue 1`] = ` exports[`GitHub porcelain API has wrappers for Issues 1`] = `
Object { Object {
"address": Object { "address": Object {
"id": "https://github.com/sourcecred/example-repo/issues/1", "id": "https://github.com/sourcecred/example-repo/issues/1",
@ -52,7 +52,7 @@ Object {
} }
`; `;
exports[`GitHub porcelain API PullRequest 1`] = ` exports[`GitHub porcelain API has wrappers for PullRequests 1`] = `
Object { Object {
"address": Object { "address": Object {
"id": "https://github.com/sourcecred/example-repo/pull/3", "id": "https://github.com/sourcecred/example-repo/pull/3",

View File

@ -11,6 +11,9 @@ import type {
NodeType, NodeType,
IssueNodePayload, IssueNodePayload,
PullRequestNodePayload, PullRequestNodePayload,
PullRequestReviewNodePayload,
PullRequestReviewCommentNodePayload,
PullRequestReviewState,
CommentNodePayload, CommentNodePayload,
AuthorNodePayload, AuthorNodePayload,
AuthorSubtype, AuthorSubtype,
@ -23,9 +26,17 @@ import {
AUTHOR_NODE_TYPE, AUTHOR_NODE_TYPE,
ISSUE_NODE_TYPE, ISSUE_NODE_TYPE,
PULL_REQUEST_NODE_TYPE, PULL_REQUEST_NODE_TYPE,
PULL_REQUEST_REVIEW_NODE_TYPE,
PULL_REQUEST_REVIEW_COMMENT_NODE_TYPE,
} from "./types"; } from "./types";
export type Entity = Issue | PullRequest | Comment | Author; export type Entity =
| Issue
| PullRequest
| Comment
| Author
| PullRequestReview
| PullRequestReviewComment;
function assertEntityType(e: Entity, t: NodeType) { function assertEntityType(e: Entity, t: NodeType) {
if (e.type() !== t) { if (e.type() !== t) {
@ -105,7 +116,12 @@ class GithubEntity<T: NodePayload> {
} }
class Post< class Post<
T: IssueNodePayload | PullRequestNodePayload | CommentNodePayload T:
| IssueNodePayload
| PullRequestNodePayload
| CommentNodePayload
| PullRequestReviewNodePayload
| PullRequestReviewCommentNodePayload
> extends GithubEntity<T> { > extends GithubEntity<T> {
authors(): Author[] { authors(): Author[] {
return this.graph return this.graph
@ -159,6 +175,14 @@ export class PullRequest extends Commentable<PullRequestNodePayload> {
title(): string { title(): string {
return this.node().payload.title; return this.node().payload.title;
} }
reviews(): PullRequestReview[] {
return this.graph
.neighborhood(this.nodeAddress, {
edgeType: CONTAINS_EDGE_TYPE,
nodeType: PULL_REQUEST_REVIEW_NODE_TYPE,
})
.map(({neighbor}) => new PullRequestReview(this.graph, neighbor));
}
} }
export class Issue extends Commentable<IssueNodePayload> { export class Issue extends Commentable<IssueNodePayload> {
@ -180,3 +204,31 @@ export class Comment extends Post<CommentNodePayload> {
return (e: any); return (e: any);
} }
} }
export class PullRequestReview extends Post<PullRequestReviewNodePayload> {
static from(e: Entity): PullRequestReview {
assertEntityType(e, PULL_REQUEST_REVIEW_NODE_TYPE);
return (e: any);
}
state(): PullRequestReviewState {
return this.node().payload.state;
}
comments(): PullRequestReviewComment[] {
return this.graph
.neighborhood(this.nodeAddress, {
edgeType: CONTAINS_EDGE_TYPE,
nodeType: PULL_REQUEST_REVIEW_COMMENT_NODE_TYPE,
})
.map(({neighbor}) => new PullRequestReviewComment(this.graph, neighbor));
}
}
export class PullRequestReviewComment extends Post<
PullRequestReviewCommentNodePayload
> {
static from(e: Entity): PullRequestReviewComment {
assertEntityType(e, PULL_REQUEST_REVIEW_COMMENT_NODE_TYPE);
return (e: any);
}
}

View File

@ -19,8 +19,8 @@ describe("GitHub porcelain API", () => {
} }
return result; return result;
} }
describe("has wrappers for", () => {
it("Issue", () => { it("Issues", () => {
const issue = issueOrPRByNumber(1); const issue = issueOrPRByNumber(1);
expect(issue.title()).toBe("An example issue."); expect(issue.title()).toBe("An example issue.");
expect(issue.body()).toBe("This is just an example issue."); expect(issue.body()).toBe("This is just an example issue.");
@ -34,9 +34,10 @@ describe("GitHub porcelain API", () => {
expect(issue.authors().map((x) => x.login())).toEqual(["decentralion"]); expect(issue.authors().map((x) => x.login())).toEqual(["decentralion"]);
}); });
it("PullRequest", () => { it("PullRequests", () => {
const pullRequest = issueOrPRByNumber(3); const pullRequest = issueOrPRByNumber(3);
expect(pullRequest.body()).toBe("Oh look, it's a pull request."); expect(pullRequest.body()).toBe("Oh look, it's a pull request.");
expect(pullRequest.title()).toBe("Add README, merge via PR.");
expect(pullRequest.url()).toBe( expect(pullRequest.url()).toBe(
"https://github.com/sourcecred/example-repo/pull/3" "https://github.com/sourcecred/example-repo/pull/3"
); );
@ -46,7 +47,29 @@ describe("GitHub porcelain API", () => {
expect(pullRequest.address()).toEqual(pullRequest.node().address); expect(pullRequest.address()).toEqual(pullRequest.node().address);
}); });
it("Comment", () => { it("Pull Request Reviews", () => {
const pr = PullRequest.from(issueOrPRByNumber(5));
const reviews = pr.reviews();
expect(reviews).toHaveLength(2);
expect(reviews[0].state()).toBe("CHANGES_REQUESTED");
expect(reviews[1].state()).toBe("APPROVED");
});
it("Pull Request Review Comments", () => {
const pr = PullRequest.from(issueOrPRByNumber(5));
const reviews = pr.reviews();
expect(reviews).toHaveLength(2);
const comments = reviews[0].comments();
expect(comments).toHaveLength(1);
const comment = comments[0];
expect(comment.url()).toBe(
"https://github.com/sourcecred/example-repo/pull/5#discussion_r171460198"
);
expect(comment.body()).toBe("seems a bit capricious");
expect(comment.authors().map((a) => a.login())).toEqual(["wchargin"]);
});
it("Comments", () => {
const issue = issueOrPRByNumber(6); const issue = issueOrPRByNumber(6);
const comments = issue.comments(); const comments = issue.comments();
expect(comments.length).toMatchSnapshot(); expect(comments.length).toMatchSnapshot();
@ -61,7 +84,7 @@ describe("GitHub porcelain API", () => {
expect(comment.authors().map((x) => x.login())).toEqual(["decentralion"]); expect(comment.authors().map((x) => x.login())).toEqual(["decentralion"]);
}); });
it("Author", () => { it("Authors", () => {
const authors = repo.authors(); const authors = repo.authors();
// So we don't need to manually update the test if a new person posts // So we don't need to manually update the test if a new person posts
expect(authors.length).toMatchSnapshot(); expect(authors.length).toMatchSnapshot();
@ -76,9 +99,10 @@ describe("GitHub porcelain API", () => {
expect(decentralion.node()).toMatchSnapshot(); expect(decentralion.node()).toMatchSnapshot();
expect(decentralion.address()).toEqual(decentralion.node().address); expect(decentralion.address()).toEqual(decentralion.node().address);
}); });
});
describe("type coercion", () => { describe("has type coercion that", () => {
it("type coercion works when typed correctly", () => { it("allows refining types when correct", () => {
const _unused_issue: Issue = Issue.from(issueOrPRByNumber(1)); const _unused_issue: Issue = Issue.from(issueOrPRByNumber(1));
const _unused_pr: PullRequest = PullRequest.from(issueOrPRByNumber(3)); const _unused_pr: PullRequest = PullRequest.from(issueOrPRByNumber(3));
const _unused_author: Author = Author.from( const _unused_author: Author = Author.from(
@ -88,7 +112,7 @@ describe("GitHub porcelain API", () => {
issueOrPRByNumber(2).comments()[0] issueOrPRByNumber(2).comments()[0]
); );
}); });
it("type coercion throws error when typed incorrectly", () => { it("throws an error on bad type refinement", () => {
expect(() => PullRequest.from(issueOrPRByNumber(1))).toThrowError( expect(() => PullRequest.from(issueOrPRByNumber(1))).toThrowError(
"to have type PULL_REQUEST" "to have type PULL_REQUEST"
); );