diff --git a/.gitignore b/.gitignore index 698139ed5..482503b8f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ yalc.lock .idea cypress/videos -cypress/screenshots \ No newline at end of file +cypress/screenshots +teste2e/cypress/downloads diff --git a/src/containers/Comments/Download/hooks.js b/src/containers/Comments/Download/hooks.js index a49c5952e..eef384d48 100644 --- a/src/containers/Comments/Download/hooks.js +++ b/src/containers/Comments/Download/hooks.js @@ -44,7 +44,10 @@ export function useDownloadCommentsTimestamps(recordToken) { [recordToken] ); const allCommentsBySection = useSelector(commentsSelector); - const comments = Object.values(allCommentsBySection).flat(); + const comments = useMemo( + () => Object.values(allCommentsBySection).flat(), + [allCommentsBySection] + ); const commentsLength = comments?.length || 0; const multiPage = commentsLength > TIMESTAMPS_PAGE_SIZE; const onFetchCommentsTimestamps = useAction(act.onFetchCommentsTimestamps); diff --git a/teste2e/cypress/e2e/comments/comments.js b/teste2e/cypress/e2e/comments/comments.js index c63d338a4..faa7c0a98 100644 --- a/teste2e/cypress/e2e/comments/comments.js +++ b/teste2e/cypress/e2e/comments/comments.js @@ -1,8 +1,9 @@ import { buildProposal, buildComment } from "../../support/generate"; -import { shortRecordToken } from "../../utils"; +import { shortRecordToken, getFirstShortProposalToken } from "../../utils"; +import path from "path"; describe("User comments", () => { - it("Can not comment if hasn't paid the paywall", () => { + it("Shouldn't allow submitting new comments if paywall not paid", () => { cy.server(); // create proposal const user = { @@ -37,8 +38,7 @@ describe("User comments", () => { } ); }); - - it("Can comment, vote and reply on others' comments if paid the paywall", () => { + it("Should allow user who paid the paywall to add new comments & vote or reply on others' comments", () => { cy.server(); // create proposal const user = { @@ -95,8 +95,62 @@ describe("User comments", () => { ); }); }); - -describe("Failed comments", () => { +describe.only("Comments downloads", () => { + let shortToken = ""; + beforeEach(() => { + cy.server(); + cy.intercept("/api/records/v1/records").as("records"); + cy.intercept("/api/records/v1/details").as("details"); + cy.visit("/"); + cy.wait("@records").then(({ response: { body } }) => { + const { records } = body; + shortToken = getFirstShortProposalToken(records); + expect(shortToken, "You should have at least one record Under Review.").to + .exist; + // login paid user + const user1 = { + email: "user1@example.com", + username: "user1", + password: "password" + }; + cy.login(user1); + cy.identity(); + cy.visit(`record/${shortToken}`); + const { text } = buildComment(); + cy.findByTestId(/text-area/i).type(text); + cy.route("POST", "/api/comments/v1/new").as("newComment"); + cy.findByText(/add comment/i).click(); + cy.wait("@newComment").its("status").should("eq", 200); + cy.intercept("/api/comments/v1/comments").as("comments"); + cy.intercept("/api/comments/v1/votes").as("votes"); + }); + }); + it("should publicly allow users to download comments bundle", () => { + cy.visit(`/record/${shortToken}`); + cy.wait("@details"); + cy.wait("@comments"); + cy.wait("@votes"); + cy.findByTestId("record-links").click(); + cy.findByText(/comments bundle/i).click(); + const downloadsFolder = Cypress.config("downloadsFolder"); + cy.readFile( + path.join(downloadsFolder, `${shortToken}-comments.json`) + ).should("exist"); + }); + it("should publicly allow users to download comments timestamps", () => { + cy.visit(`/record/${shortToken}`); + cy.wait("@details"); + cy.wait("@comments"); + cy.wait("@votes"); + cy.findByTestId("record-links").click(); + cy.findByText(/comments timestamps/i).click(); + const downloadsFolder = Cypress.config("downloadsFolder"); + cy.readFile( + path.join(downloadsFolder, `${shortToken}-comments-timestamps.json`) + ).should("exist"); + }); +}); +describe("Comments error handling", () => { let token = ""; beforeEach(() => { const user = { diff --git a/teste2e/cypress/e2e/proposal/detail.js b/teste2e/cypress/e2e/proposal/detail.js index 8d65ce319..d91554f68 100644 --- a/teste2e/cypress/e2e/proposal/detail.js +++ b/teste2e/cypress/e2e/proposal/detail.js @@ -1,32 +1,5 @@ -import { - PROPOSAL_METADATA_FILENAME, - VOTE_METADATA_FILENAME -} from "../../utils"; -import compose from "lodash/fp/compose"; -import filter from "lodash/fp/filter"; -import keys from "lodash/fp/keys"; -import first from "lodash/fp/first"; -import get from "lodash/fp/get"; -import find from "lodash/fp/find"; - -function findRecordFileByName(record, name) { - return compose( - find((file) => file.name === name), - get("files") - )(record); -} - -function getShortProposalToken(records = {}) { - return compose( - first, - filter( - (token) => - !findRecordFileByName(records[token], VOTE_METADATA_FILENAME) && - findRecordFileByName(records[token], PROPOSAL_METADATA_FILENAME) - ), - keys - )(records); -} +import { getFirstShortProposalToken } from "../../utils"; +import path from "path"; describe("Record Details", () => { describe("proposal details", () => { @@ -40,7 +13,7 @@ describe("Record Details", () => { cy.visit("/"); cy.wait("@records").then(({ response: { body } }) => { const { records } = body; - shortToken = getShortProposalToken(records); + shortToken = getFirstShortProposalToken(records); token = records[shortToken].censorshiprecord.token; expect( shortToken, @@ -48,11 +21,11 @@ describe("Record Details", () => { ).to.exist; }); }); - it("can render proposal correctly by short token", () => { + it("should render a propsoal with a short token", () => { cy.visit(`/record/${shortToken}`); cy.wait("@details"); }); - it("can render proposal correctly by full token", () => { + it("should render a proposal with a full token", () => { cy.visit(`/record/${token}`); cy.wait("@details"); }); @@ -77,6 +50,41 @@ describe("Record Details", () => { .and("include.text", "End Date"); }); }); + describe("proposal downloads", () => { + beforeEach(() => { + cy.visit("/"); + cy.wait("@records").then(({ response: { body } }) => { + const { records } = body; + shortToken = getFirstShortProposalToken(records); + token = records[shortToken].censorshiprecord.token; + expect( + shortToken, + "You should have at least one record Under Review." + ).to.exist; + }); + }); + it("should publicly allow to download proposal bundle", () => { + cy.visit(`/record/${shortToken}`); + cy.wait("@details"); + cy.findByTestId("record-links").click(); + cy.findByText(/proposal bundle/i).click(); + const downloadsFolder = Cypress.config("downloadsFolder"); + cy.readFile(path.join(downloadsFolder, `${shortToken}-v1.json`)).should( + "exist" + ); + }); + it("should publicly allow to download proposal timestamps", () => { + cy.visit(`/record/${shortToken}`); + cy.wait("@details"); + cy.findByTestId("record-links").click(); + cy.findByText(/proposal timestamps/i).click(); + const config = Cypress.config(); + const downloadsFolder = Cypress.config("downloadsFolder"); + cy.readFile( + path.join(downloadsFolder, `${shortToken}-v1-timestamps.json`) + ).should("exist"); + }); + }); describe("invalid proposal rendering", () => { it("should dislpay not found message for nonexistent proposals", () => { cy.visit("/record/invalidtoken"); @@ -94,7 +102,7 @@ describe("Record Details", () => { cy.visit("/admin/records"); cy.wait("@records").then(({ response: { body } }) => { const { records } = body; - shortToken = getShortProposalToken(records); + shortToken = getFirstShortProposalToken(records); expect(shortToken, "You should have at least one unvetted record.").to .exist; }); diff --git a/teste2e/cypress/utils.js b/teste2e/cypress/utils.js index 04dec002a..ba7be0685 100644 --- a/teste2e/cypress/utils.js +++ b/teste2e/cypress/utils.js @@ -1,7 +1,13 @@ import CryptoJS from "crypto-js"; -import get from "lodash/fp/get"; import * as pki from "./pki"; import MerkleTree from "./merkle"; +import compose from "lodash/fp/compose"; +import filter from "lodash/fp/filter"; +import keys from "lodash/fp/keys"; +import first from "lodash/fp/first"; +import get from "lodash/fp/get"; +import find from "lodash/fp/find"; +import path from "path"; const PROPOSAL_TYPE_REGULAR = 1; const PROPOSAL_TYPE_RFP = 2; @@ -32,6 +38,23 @@ const PROPOSAL_STATUS_PUBLIC = 2; const PROPOSAL_STATUS_CENSORED = 3; const PROPOSAL_STATUS_ARCHIVED = 4; +const findRecordFileByName = (record, name) => + compose( + find((file) => file.name === name), + get("files") + )(record); + +export const getFirstShortProposalToken = (records = {}) => + compose( + first, + filter( + (token) => + !findRecordFileByName(records[token], VOTE_METADATA_FILENAME) && + findRecordFileByName(records[token], PROPOSAL_METADATA_FILENAME) + ), + keys + )(records); + export const requestWithCsrfToken = (url, body, failOnStatusCode = true) => cy.request("/api").then((res) => cy.request({