From 7a11483415281f8ddd0a9fdb08805659d33e67ff Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Mon, 15 Jul 2024 12:15:35 +0000 Subject: [PATCH 1/9] test for large proposal comment, and test that the submitted transaction contains the pasted comment text --- .../tests/proposal/comment.spec.js | 229 ++++++++++++++++++ .../tests/proposal/proposals.spec.js | 79 ------ playwright-tests/util/transaction.js | 35 +-- 3 files changed, 250 insertions(+), 93 deletions(-) create mode 100644 playwright-tests/tests/proposal/comment.spec.js diff --git a/playwright-tests/tests/proposal/comment.spec.js b/playwright-tests/tests/proposal/comment.spec.js new file mode 100644 index 000000000..7f26cd282 --- /dev/null +++ b/playwright-tests/tests/proposal/comment.spec.js @@ -0,0 +1,229 @@ +import { test, expect } from "@playwright/test"; +import { pauseIfVideoRecording } from "../../testUtils.js"; +import { setCommitWritePermissionDontAskAgainCacheValues } from "../../util/cache.js"; +import { + mockTransactionSubmitRPCResponses, + decodeResultJSON, + encodeResultJSON, +} from "../../util/transaction.js"; + +test.describe("Don't ask again enabled", () => { + test.use({ + contextOptions: { + permissions: ["clipboard-read", "clipboard-write"], + }, + storageState: + "playwright-tests/storage-states/wallet-connected-with-devhub-access-key.json", + }); + test("should add comment to a proposal", async ({ page }) => { + await page.goto("/devhub.near/widget/app?page=proposal&id=17"); + const widgetSrc = + "devhub.near/widget/devhub.entity.proposal.ComposeComment"; + + const delay_milliseconds_between_keypress_when_typing = 0; + const commentArea = await page + .frameLocator("iframe") + .locator(".CodeMirror textarea"); + await commentArea.focus(); + const text = "Comment testing"; + await commentArea.pressSequentially(text, { + delay: delay_milliseconds_between_keypress_when_typing, + }); + await commentArea.blur(); + await pauseIfVideoRecording(page); + + const account = "petersalomonsen.near"; + await setCommitWritePermissionDontAskAgainCacheValues({ + page, + widgetSrc, + accountId: account, + }); + + await mockTransactionSubmitRPCResponses( + page, + async ({ route, request, transaction_completed, last_receiver_id }) => { + const postData = request.postDataJSON(); + const args_base64 = postData.params?.args_base64; + if (transaction_completed && args_base64) { + const args = atob(args_base64); + if ( + postData.params.account_id === "social.near" && + postData.params.method_name === "get" && + args === `{"keys":["${account}/post/**"]}` + ) { + const response = await route.fetch(); + const json = await response.json(); + const resultObj = decodeResultJSON(json.result.result); + resultObj[account].post.main = JSON.stringify({ + text: text, + }); + json.result.result = encodeResultJSON(resultObj); + await route.fulfill({ response, json }); + return; + } else { + await route.continue(); + } + } else { + await route.continue(); + } + } + ); + const commentButton = await page.getByRole("button", { name: "Comment" }); + await expect(commentButton).toBeAttached(); + await commentButton.scrollIntoViewIfNeeded(); + await commentButton.click(); + await expect( + await page.frameLocator("iframe").locator(".CodeMirror") + ).toContainText(text); + const loadingIndicator = await page.locator(".comment-btn-spinner"); + await expect(loadingIndicator).toBeAttached(); + await loadingIndicator.waitFor({ state: "detached", timeout: 30000 }); + await expect(loadingIndicator).not.toBeVisible(); + const transaction_successful_toast = await page.getByText( + "Comment Submitted Successfully", + { exact: true } + ); + await expect(transaction_successful_toast).toBeVisible(); + + await expect(transaction_successful_toast).not.toBeAttached(); + await expect( + await page.frameLocator("iframe").locator(".CodeMirror") + ).not.toContainText(text); + await expect( + await page.frameLocator("iframe").locator(".CodeMirror") + ).toContainText("Add your comment here..."); + await pauseIfVideoRecording(page); + }); + test("should paste a long comment to a proposal", async ({ page }) => { + test.setTimeout(70000); + await page.goto("/devhub.near/widget/app?page=proposal&id=17"); + const widgetSrc = + "devhub.near/widget/devhub.entity.proposal.ComposeComment"; + + const commentText = + "Hi @petersalomonsen.near – congratulations! This message confirms your funding request approval by @neardevdao.near. We're excited to sponsor your work! This approval follows our review process involving various Work Groups and DevDAO Moderators, as outlined in our [guidelines](/devhub.near/widget/app?page=community&handle=developer-dao&tab=Funding). Please note that the funding distribution is contingent on successfully passing our KYC/B and paperwork process.\n\nHere’s what to expect:\n\n**Funding Steps**\n\n1. **KYC/KYB Verification:** A DevDAO Moderator will move your proposal to the Payment Processing Stage and verify that you have completed verification to ensure compliance. If you are not verified, your DevHub Moderator will contact you on Telegram with instructions on how to proceed. To receive funding, you must get verified through Fractal, a trusted third-party identification verification solution. Your verification badge is valid for 365 days and needs renewal upon expiration OR if your personal information changes, such as your name, address, or ID expiration.\n2. **Information Collection:** Once verified, a DevDAO Moderator will contact you via Telegram and request that you complete the Funding Request Form using Airtable.\n3. **Processing:** Our legal team will verify your application details to ensure compliance. They will then send you an email requesting your signature for the underlying agreement via Ironclad.\n4. **Invoicing & Payment:** Once we receive your signed agreement, our finance team will email you instructions to submit the final invoice using Request Finance. Once we receive your invoice, our finance team will send a test transaction confirmation email. Once you confirm the test transaction, we will distribute the funds and post a payment link on your proposal.\n\n**Funding Conversion Notice**\n\nOnce you receive your funding, we urge you to exercise caution if attempting to convert your funds. Some third-party tools may impose significant swapping fees.\n\n**Visibility**\n\nWe track the funding process on each proposal using the timeline and comments. However, you are welcome to reach out to the DevDAO Moderator with any questions. \n\n**Timeline**\n\nTypically, funds are disbursed within 10 business days, but the timeline can vary depending on the project's complexity and paperwork. Your DevDAO Moderator will keep you updated."; + await page.evaluate(async (text) => { + await navigator.clipboard.writeText(text); + }, commentText); + + const commentArea = await page + .frameLocator("iframe") + .locator(".CodeMirror textarea"); + + await pauseIfVideoRecording(page); + await commentArea.focus(); + + const isMac = process.platform === "darwin"; + + if (isMac) { + await page.keyboard.down("Meta"); // Command key on macOS + await page.keyboard.press("a"); + await page.keyboard.press("v"); + await page.keyboard.up("Meta"); + } else { + await page.keyboard.down("Control"); // Control key on Windows/Linux + await page.keyboard.press("a"); + await page.keyboard.press("v"); + await page.keyboard.up("Control"); + } + + await commentArea.blur(); + await pauseIfVideoRecording(page); + + const account = "petersalomonsen.near"; + await setCommitWritePermissionDontAskAgainCacheValues({ + page, + widgetSrc, + accountId: account, + }); + + const transactionMockStatus = await mockTransactionSubmitRPCResponses( + page, + async ({ route, request, transaction_completed, last_transaction }) => { + const postData = request.postDataJSON(); + const args_base64 = postData.params?.args_base64; + + if (transaction_completed && args_base64) { + const args = atob(args_base64); + if ( + postData.params.account_id === "social.near" && + postData.params.method_name === "get" && + args === `{"keys":["${account}/post/**"]}` + ) { + const response = await route.fetch(); + const json = await response.json(); + const resultObj = decodeResultJSON(json.result.result); + + resultObj[account].post.main = JSON.stringify({ + text: commentText, + }); + json.result.result = encodeResultJSON(resultObj); + completedPromiseResolve(last_transaction); + await route.fulfill({ response, json }); + return; + } else { + await route.continue(); + } + } else { + await route.continue(); + } + } + ); + + const commentButton = await page.getByRole("button", { name: "Comment" }); + await expect(commentButton).toBeAttached(); + await commentButton.scrollIntoViewIfNeeded(); + + let submittedTransactionJsonObject; + await page.route("https://api.near.social/index", async (route) => { + const request = route.request(); + if (transactionMockStatus.transaction_completed) { + const lastTransactionParamBuffer = Buffer.from( + transactionMockStatus.last_transaction.params[0], + "base64" + ); + + const transactionDataJsonStartIndex = + lastTransactionParamBuffer.indexOf('{"data":'); + const transactionDataJsonEndIndex = + lastTransactionParamBuffer.indexOf('}}]"}}}}') + '}}]"}}}}'.length; + const transactionDataJsonString = lastTransactionParamBuffer.subarray( + transactionDataJsonStartIndex, + transactionDataJsonEndIndex + ); + + submittedTransactionJsonObject = JSON.parse( + transactionDataJsonString.toString() + ); + console.log("SOCIAL_INDEX", request.postData()); + } + route.continue(); + }); + + await commentButton.click(); + + const loadingIndicator = await page.locator(".comment-btn-spinner"); + await expect(loadingIndicator).toBeAttached(); + await loadingIndicator.waitFor({ state: "detached", timeout: 30000 }); + await expect(loadingIndicator).not.toBeVisible(); + const transaction_successful_toast = await page.getByText( + "Comment Submitted Successfully", + { exact: true } + ); + await expect(transaction_successful_toast).toBeVisible(); + + await expect(transaction_successful_toast).not.toBeAttached(); + await expect( + await page.frameLocator("iframe").locator(".CodeMirror") + ).not.toContainText(commentText); + await expect( + await page.frameLocator("iframe").locator(".CodeMirror") + ).toContainText("Add your comment here..."); + + const submittedComment = JSON.parse( + submittedTransactionJsonObject.data["petersalomonsen.near"].post.comment + ); + expect(submittedComment.text).toEqual(commentText); + await pauseIfVideoRecording(page); + }); +}); diff --git a/playwright-tests/tests/proposal/proposals.spec.js b/playwright-tests/tests/proposal/proposals.spec.js index fc7ac569f..de57c3a89 100644 --- a/playwright-tests/tests/proposal/proposals.spec.js +++ b/playwright-tests/tests/proposal/proposals.spec.js @@ -176,85 +176,6 @@ test.describe("Don't ask again enabled", () => { await page.waitForTimeout(1000); await pauseIfVideoRecording(page); }); - test("should add comment on a proposal", async ({ page }) => { - await page.goto("/devhub.near/widget/app?page=proposal&id=17"); - const widgetSrc = - "devhub.near/widget/devhub.entity.proposal.ComposeComment"; - - const delay_milliseconds_between_keypress_when_typing = 0; - const commentArea = await page - .frameLocator("iframe") - .locator(".CodeMirror textarea"); - await commentArea.focus(); - const text = "Comment testing"; - await commentArea.pressSequentially(text, { - delay: delay_milliseconds_between_keypress_when_typing, - }); - await commentArea.blur(); - await pauseIfVideoRecording(page); - - const account = "petersalomonsen.near"; - await setCommitWritePermissionDontAskAgainCacheValues({ - page, - widgetSrc, - accountId: account, - }); - - await mockTransactionSubmitRPCResponses( - page, - async ({ route, request, transaction_completed, last_receiver_id }) => { - const postData = request.postDataJSON(); - const args_base64 = postData.params?.args_base64; - if (transaction_completed && args_base64) { - const args = atob(args_base64); - if ( - postData.params.account_id === "social.near" && - postData.params.method_name === "get" && - args === `{"keys":["${account}/post/**"]}` - ) { - const response = await route.fetch(); - const json = await response.json(); - const resultObj = decodeResultJSON(json.result.result); - resultObj[account].post.main = JSON.stringify({ - text: text, - }); - json.result.result = encodeResultJSON(resultObj); - await route.fulfill({ response, json }); - return; - } else { - await route.continue(); - } - } else { - await route.continue(); - } - } - ); - const commentButton = await page.getByRole("button", { name: "Comment" }); - await expect(commentButton).toBeAttached(); - await commentButton.scrollIntoViewIfNeeded(); - await commentButton.click(); - await expect( - await page.frameLocator("iframe").locator(".CodeMirror") - ).toContainText(text); - const loadingIndicator = await page.locator(".comment-btn-spinner"); - await expect(loadingIndicator).toBeAttached(); - await loadingIndicator.waitFor({ state: "detached", timeout: 30000 }); - await expect(loadingIndicator).not.toBeVisible(); - const transaction_successful_toast = await page.getByText( - "Comment Submitted Successfully", - { exact: true } - ); - await expect(transaction_successful_toast).toBeVisible(); - - await expect(transaction_successful_toast).not.toBeAttached(); - await expect( - await page.frameLocator("iframe").locator(".CodeMirror") - ).not.toContainText(text); - await expect( - await page.frameLocator("iframe").locator(".CodeMirror") - ).toContainText("Add your comment here..."); - await pauseIfVideoRecording(page); - }); }); test.describe('Moderator with "Don\'t ask again" enabled', () => { diff --git a/playwright-tests/util/transaction.js b/playwright-tests/util/transaction.js index fa6d5a41f..4c02b0a5e 100644 --- a/playwright-tests/util/transaction.js +++ b/playwright-tests/util/transaction.js @@ -62,9 +62,12 @@ export function encodeResultJSON(resultObj) { } export async function mockTransactionSubmitRPCResponses(page, customhandler) { - let transaction_completed = false; - let last_receiver_id; - let lastViewedAccessKey; + const status = { + transaction_completed: false, + last_receiver_id: undefined, + lastViewedAccessKey: undefined, + last_transaction: undefined, + }; await page.route( (url) => url.origin === "https://rpc.mainnet.near.org" || @@ -89,10 +92,10 @@ export async function mockTransactionSubmitRPCResponses(page, customhandler) { const response = await route.fetch(); const json = await response.json(); - lastViewedAccessKey = access_keys.find( + status.lastViewedAccessKey = access_keys.find( (k) => k.public_key === requestPostData.params.public_key ); - json.result = lastViewedAccessKey.access_key; + json.result = status.lastViewedAccessKey.access_key; delete json.error; await route.fulfill({ response, json }); @@ -111,9 +114,11 @@ export async function mockTransactionSubmitRPCResponses(page, customhandler) { await route.fulfill({ response, json }); } else if (requestPostData.method == "broadcast_tx_commit") { - transaction_completed = false; - last_receiver_id = - lastViewedAccessKey.access_key.permission.FunctionCall.receiver_id; + status.transaction_completed = false; + status.last_receiver_id = + status.lastViewedAccessKey.access_key.permission.FunctionCall.receiver_id; + status.last_transaction = requestPostData; + await page.waitForTimeout(1000); await route.fulfill({ @@ -124,7 +129,7 @@ export async function mockTransactionSubmitRPCResponses(page, customhandler) { SuccessValue: "", }, transaction: { - receiver_id: last_receiver_id, + receiver_id: status.last_receiver_id, }, transaction_outcome: { proof: [], @@ -146,9 +151,9 @@ export async function mockTransactionSubmitRPCResponses(page, customhandler) { }, }, }); - transaction_completed = true; + status.transaction_completed = true; } else if ( - transaction_completed && + status.transaction_completed && requestPostData.params && requestPostData.params.method_name === "get_all_post_ids" ) { @@ -166,7 +171,7 @@ export async function mockTransactionSubmitRPCResponses(page, customhandler) { ); await route.fulfill({ response, json }); } else if ( - transaction_completed && + status.transaction_completed && requestPostData.params && requestPostData.params.method_name === "get_post" ) { @@ -187,8 +192,9 @@ export async function mockTransactionSubmitRPCResponses(page, customhandler) { await customhandler({ route, request, - transaction_completed, - last_receiver_id, + transaction_completed: status.transaction_completed, + last_receiver_id: status.last_receiver_id, + last_transaction: status.last_transaction, requestPostData, }); } else { @@ -196,4 +202,5 @@ export async function mockTransactionSubmitRPCResponses(page, customhandler) { } } ); + return status; } From 0a21b10f1cec4cd5578f6a856cc19909b312ed0a Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Mon, 15 Jul 2024 14:29:46 +0000 Subject: [PATCH 2/9] wait for submitted comment to be visible --- .../tests/proposal/comment.spec.js | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/playwright-tests/tests/proposal/comment.spec.js b/playwright-tests/tests/proposal/comment.spec.js index 7f26cd282..d3d4dde27 100644 --- a/playwright-tests/tests/proposal/comment.spec.js +++ b/playwright-tests/tests/proposal/comment.spec.js @@ -176,7 +176,6 @@ test.describe("Don't ask again enabled", () => { let submittedTransactionJsonObject; await page.route("https://api.near.social/index", async (route) => { - const request = route.request(); if (transactionMockStatus.transaction_completed) { const lastTransactionParamBuffer = Buffer.from( transactionMockStatus.last_transaction.params[0], @@ -195,9 +194,21 @@ test.describe("Don't ask again enabled", () => { submittedTransactionJsonObject = JSON.parse( transactionDataJsonString.toString() ); - console.log("SOCIAL_INDEX", request.postData()); + + const response = await route.fetch(); + const json = await response.json(); + json.push({ + accountId: "theori.near", + blockHeight: 121684809, + value: { + type: "md", + }, + }); + + await route.fulfill({ json }); + } else { + await route.continue(); } - route.continue(); }); await commentButton.click(); @@ -224,6 +235,13 @@ test.describe("Don't ask again enabled", () => { submittedTransactionJsonObject.data["petersalomonsen.near"].post.comment ); expect(submittedComment.text).toEqual(commentText); + const commentElement = await page.locator("#theorinear121684809"); + await expect(commentElement).toBeVisible(); + await commentElement.scrollIntoViewIfNeeded(); + await expect(commentElement).toContainText( + "Typically, funds are disbursed within 10 business days, but the timeline can vary depending on the project's complexity and paperwork. Your DevDAO Moderator will keep you updated." + ); + await pauseIfVideoRecording(page); }); }); From 92caa810cb5ea5bbafa0df15d15bc46f2422518a Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Mon, 15 Jul 2024 14:44:59 +0000 Subject: [PATCH 3/9] reload the page and expect the comment field to be empty ( fails ) --- .../tests/proposal/comment.spec.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/playwright-tests/tests/proposal/comment.spec.js b/playwright-tests/tests/proposal/comment.spec.js index d3d4dde27..e07cce378 100644 --- a/playwright-tests/tests/proposal/comment.spec.js +++ b/playwright-tests/tests/proposal/comment.spec.js @@ -94,7 +94,7 @@ test.describe("Don't ask again enabled", () => { ).toContainText("Add your comment here..."); await pauseIfVideoRecording(page); }); - test("should paste a long comment to a proposal", async ({ page }) => { + test("should paste a long comment to a proposal, see that the comment appears after submission, and that the comment field is cleared, even after reloading the page", async ({ page }) => { test.setTimeout(70000); await page.goto("/devhub.near/widget/app?page=proposal&id=17"); const widgetSrc = @@ -235,13 +235,27 @@ test.describe("Don't ask again enabled", () => { submittedTransactionJsonObject.data["petersalomonsen.near"].post.comment ); expect(submittedComment.text).toEqual(commentText); - const commentElement = await page.locator("#theorinear121684809"); + let commentElement = await page.locator("#theorinear121684809"); await expect(commentElement).toBeVisible(); await commentElement.scrollIntoViewIfNeeded(); await expect(commentElement).toContainText( "Typically, funds are disbursed within 10 business days, but the timeline can vary depending on the project's complexity and paperwork. Your DevDAO Moderator will keep you updated." ); + await page.reload(); + + commentElement = await page.locator("#theorinear121684809"); + await expect(commentElement).toBeVisible({timeout: 20000}); + await commentElement.scrollIntoViewIfNeeded(); + await expect(commentElement).toContainText( + "Typically, funds are disbursed within 10 business days, but the timeline can vary depending on the project's complexity and paperwork. Your DevDAO Moderator will keep you updated." + ); + + await page.waitForTimeout(5000); + await expect( + await page.frameLocator("iframe").locator(".CodeMirror") + ).toContainText("Add your comment here..."); + await pauseIfVideoRecording(page); }); }); From 7ca5c05a8c87c9b23fbf7f55e99859a6e12aff59 Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Mon, 15 Jul 2024 14:46:19 +0000 Subject: [PATCH 4/9] fmt --- playwright-tests/tests/proposal/comment.spec.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/playwright-tests/tests/proposal/comment.spec.js b/playwright-tests/tests/proposal/comment.spec.js index e07cce378..9dd9524c6 100644 --- a/playwright-tests/tests/proposal/comment.spec.js +++ b/playwright-tests/tests/proposal/comment.spec.js @@ -94,7 +94,9 @@ test.describe("Don't ask again enabled", () => { ).toContainText("Add your comment here..."); await pauseIfVideoRecording(page); }); - test("should paste a long comment to a proposal, see that the comment appears after submission, and that the comment field is cleared, even after reloading the page", async ({ page }) => { + test("should paste a long comment to a proposal, see that the comment appears after submission, and that the comment field is cleared, even after reloading the page", async ({ + page, + }) => { test.setTimeout(70000); await page.goto("/devhub.near/widget/app?page=proposal&id=17"); const widgetSrc = @@ -245,7 +247,7 @@ test.describe("Don't ask again enabled", () => { await page.reload(); commentElement = await page.locator("#theorinear121684809"); - await expect(commentElement).toBeVisible({timeout: 20000}); + await expect(commentElement).toBeVisible({ timeout: 20000 }); await commentElement.scrollIntoViewIfNeeded(); await expect(commentElement).toContainText( "Typically, funds are disbursed within 10 business days, but the timeline can vary depending on the project's complexity and paperwork. Your DevDAO Moderator will keep you updated." From 4eac52acba30929770a2d828e215cf7d32d4146d Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Mon, 15 Jul 2024 18:47:51 +0000 Subject: [PATCH 5/9] stabilize test to always fail at the same place --- .../tests/proposal/comment.spec.js | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/playwright-tests/tests/proposal/comment.spec.js b/playwright-tests/tests/proposal/comment.spec.js index 9dd9524c6..e5b3affe4 100644 --- a/playwright-tests/tests/proposal/comment.spec.js +++ b/playwright-tests/tests/proposal/comment.spec.js @@ -97,11 +97,15 @@ test.describe("Don't ask again enabled", () => { test("should paste a long comment to a proposal, see that the comment appears after submission, and that the comment field is cleared, even after reloading the page", async ({ page, }) => { - test.setTimeout(70000); + test.setTimeout(2 * 60000); await page.goto("/devhub.near/widget/app?page=proposal&id=17"); const widgetSrc = "devhub.near/widget/devhub.entity.proposal.ComposeComment"; + let commentButton = await page.getByRole("button", { name: "Comment" }); + await expect(commentButton).toBeAttached({ timeout: 10000 }); + await commentButton.scrollIntoViewIfNeeded(); + const commentText = "Hi @petersalomonsen.near – congratulations! This message confirms your funding request approval by @neardevdao.near. We're excited to sponsor your work! This approval follows our review process involving various Work Groups and DevDAO Moderators, as outlined in our [guidelines](/devhub.near/widget/app?page=community&handle=developer-dao&tab=Funding). Please note that the funding distribution is contingent on successfully passing our KYC/B and paperwork process.\n\nHere’s what to expect:\n\n**Funding Steps**\n\n1. **KYC/KYB Verification:** A DevDAO Moderator will move your proposal to the Payment Processing Stage and verify that you have completed verification to ensure compliance. If you are not verified, your DevHub Moderator will contact you on Telegram with instructions on how to proceed. To receive funding, you must get verified through Fractal, a trusted third-party identification verification solution. Your verification badge is valid for 365 days and needs renewal upon expiration OR if your personal information changes, such as your name, address, or ID expiration.\n2. **Information Collection:** Once verified, a DevDAO Moderator will contact you via Telegram and request that you complete the Funding Request Form using Airtable.\n3. **Processing:** Our legal team will verify your application details to ensure compliance. They will then send you an email requesting your signature for the underlying agreement via Ironclad.\n4. **Invoicing & Payment:** Once we receive your signed agreement, our finance team will email you instructions to submit the final invoice using Request Finance. Once we receive your invoice, our finance team will send a test transaction confirmation email. Once you confirm the test transaction, we will distribute the funds and post a payment link on your proposal.\n\n**Funding Conversion Notice**\n\nOnce you receive your funding, we urge you to exercise caution if attempting to convert your funds. Some third-party tools may impose significant swapping fees.\n\n**Visibility**\n\nWe track the funding process on each proposal using the timeline and comments. However, you are welcome to reach out to the DevDAO Moderator with any questions. \n\n**Timeline**\n\nTypically, funds are disbursed within 10 business days, but the timeline can vary depending on the project's complexity and paperwork. Your DevDAO Moderator will keep you updated."; await page.evaluate(async (text) => { @@ -172,11 +176,10 @@ test.describe("Don't ask again enabled", () => { } ); - const commentButton = await page.getByRole("button", { name: "Comment" }); - await expect(commentButton).toBeAttached(); - await commentButton.scrollIntoViewIfNeeded(); - - let submittedTransactionJsonObject; + let submittedTransactionJsonObjectPromiseResolve; + let submittedTransactionJsonObjectPromise = new Promise( + (r) => (submittedTransactionJsonObjectPromiseResolve = r) + ); await page.route("https://api.near.social/index", async (route) => { if (transactionMockStatus.transaction_completed) { const lastTransactionParamBuffer = Buffer.from( @@ -193,8 +196,8 @@ test.describe("Don't ask again enabled", () => { transactionDataJsonEndIndex ); - submittedTransactionJsonObject = JSON.parse( - transactionDataJsonString.toString() + submittedTransactionJsonObjectPromiseResolve( + JSON.parse(transactionDataJsonString.toString()) ); const response = await route.fetch(); @@ -213,6 +216,7 @@ test.describe("Don't ask again enabled", () => { } }); + commentButton = await page.getByRole("button", { name: "Comment" }); await commentButton.click(); const loadingIndicator = await page.locator(".comment-btn-spinner"); @@ -233,12 +237,14 @@ test.describe("Don't ask again enabled", () => { await page.frameLocator("iframe").locator(".CodeMirror") ).toContainText("Add your comment here..."); + const submittedTransactionJsonObject = + await submittedTransactionJsonObjectPromise; const submittedComment = JSON.parse( submittedTransactionJsonObject.data["petersalomonsen.near"].post.comment ); expect(submittedComment.text).toEqual(commentText); let commentElement = await page.locator("#theorinear121684809"); - await expect(commentElement).toBeVisible(); + await expect(commentElement).toBeVisible({ timeout: 10000 }); await commentElement.scrollIntoViewIfNeeded(); await expect(commentElement).toContainText( "Typically, funds are disbursed within 10 business days, but the timeline can vary depending on the project's complexity and paperwork. Your DevDAO Moderator will keep you updated." @@ -253,6 +259,10 @@ test.describe("Don't ask again enabled", () => { "Typically, funds are disbursed within 10 business days, but the timeline can vary depending on the project's complexity and paperwork. Your DevDAO Moderator will keep you updated." ); + commentButton = await page.getByRole("button", { name: "Comment" }); + await expect(commentButton).toBeAttached({ timeout: 10000 }); + await commentButton.scrollIntoViewIfNeeded(); + await page.waitForTimeout(5000); await expect( await page.frameLocator("iframe").locator(".CodeMirror") From f9aeb4a83e3c23603df43f7f0bc2966fc13e74d7 Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Mon, 15 Jul 2024 18:48:41 +0000 Subject: [PATCH 6/9] fix issue fix #876 --- .../devhub.near/widget/devhub/entity/proposal/ComposeComment.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/instances/devhub.near/widget/devhub/entity/proposal/ComposeComment.jsx b/instances/devhub.near/widget/devhub/entity/proposal/ComposeComment.jsx index 4516df397..7e8209392 100644 --- a/instances/devhub.near/widget/devhub/entity/proposal/ComposeComment.jsx +++ b/instances/devhub.near/widget/devhub/entity/proposal/ComposeComment.jsx @@ -170,6 +170,7 @@ function composeData() { onCommit: () => { setCommentToast(true); setComment(""); + Storage.privateSet(draftKey, ""); setHandler("committed"); setTxnCreated(false); }, From 9e082e3686c3c92d3d593957ddcdd3644dc2faef Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Tue, 16 Jul 2024 20:20:30 +0000 Subject: [PATCH 7/9] applied fix for events committee, and enabled proposal comment test run --- ...continuous-integration-workflow-events.yml | 19 ++- .../entity/proposal/CommentsAndLogs.jsx | 49 ++++-- .../devhub/entity/proposal/ComposeComment.jsx | 7 +- .../components/molecule/ComposeComment.jsx | 1 + .../components/proposals/CommentsAndLogs.jsx | 160 ++++++++++-------- .../tests/proposal/comment.spec.js | 44 +++-- playwright.config.js | 11 +- 7 files changed, 184 insertions(+), 107 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow-events.yml b/.github/workflows/continuous-integration-workflow-events.yml index 555f1e1c9..5ddd3bb39 100644 --- a/.github/workflows/continuous-integration-workflow-events.yml +++ b/.github/workflows/continuous-integration-workflow-events.yml @@ -19,7 +19,24 @@ jobs: run: npm ci - name: Run code formatting check run: npm run fmt:check - + playwright-tests-proposal: + name: Proposal - Playwright tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "npm" + - name: Install dependencies + run: | + npm ci + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/mpeterdev/bos-loader/releases/download/v0.7.1/bos-loader-v0.7.1-installer.sh | sh + npx playwright install-deps + npx playwright install + - name: Run tests + run: | + INSTANCE=events npx playwright test --project=events playwright-tests/tests/proposal/comment.spec.js playwright-tests-events: name: Events Committee - Playwright tests runs-on: ubuntu-latest diff --git a/instances/events-committee.near/widget/devhub/entity/proposal/CommentsAndLogs.jsx b/instances/events-committee.near/widget/devhub/entity/proposal/CommentsAndLogs.jsx index 25951cae4..e4804ae64 100644 --- a/instances/events-committee.near/widget/devhub/entity/proposal/CommentsAndLogs.jsx +++ b/instances/events-committee.near/widget/devhub/entity/proposal/CommentsAndLogs.jsx @@ -2,6 +2,7 @@ const { getLinkUsingCurrentGateway } = VM.require( "${REPL_DEVHUB}/widget/core.lib.url" ) || { getLinkUsingCurrentGateway: () => {} }; const snapshotHistory = props.snapshotHistory; +const proposalId = props.id; const Wrapper = styled.div` position: relative; @@ -59,11 +60,10 @@ function getDifferentKeysWithValues(obj1, obj2) { if (key !== "editor_id" && obj2.hasOwnProperty(key)) { const value1 = obj1[key]; const value2 = obj2[key]; - if (Array.isArray(value1) && Array.isArray(value2)) { - const sortedValue1 = [...value1].sort(); - const sortedValue2 = [...value2].sort(); - return JSON.stringify(sortedValue1) !== JSON.stringify(sortedValue2); - } else if (typeof value1 === "object" && typeof value2 === "object") { + + if (typeof value1 === "object" && typeof value2 === "object") { + return JSON.stringify(value1) !== JSON.stringify(value2); + } else if (Array.isArray(value1) && Array.isArray(value2)) { return JSON.stringify(value1) !== JSON.stringify(value2); } else { return value1 !== value2; @@ -98,6 +98,22 @@ function sortTimelineAndComments() { ...getDifferentKeysWithValues(startingPoint, item), }; }); + + // add log for accepting terms and condition + changedKeysListWithValues.unshift({ + 0: { + key: "timestamp", + originalValue: "0", + modifiedValue: snapshotHistory[0].timestamp, + }, + 1: { + key: "terms_and_condition", + originalValue: "", + modifiedValue: "accepted", + }, + editorId: snapshotHistory[0].editor_id, + }); + State.update({ changedKeysListWithValues, snapshotHistoryLength: snapshotHistory.length, @@ -143,9 +159,7 @@ const Comment = ({ commentItem }) => { blockHeight, }; const content = JSON.parse(Social.get(item.path, blockHeight) ?? "null"); - const link = getLinkUsingCurrentGateway( - `${REPL_DEVHUB}/widget/app?page=proposal&id=${props.id}&accountId=${accountId}&blockHeight=${blockHeight}` - ); + const link = `https://${REPL_DEVHUB}.page/proposal/${proposalId}?accountId=${accountId}&blockHeight=${blockHeight}`; const hightlightComment = parseInt(props.blockHeight ?? "") === blockHeight && props.accountId === accountId; @@ -162,6 +176,7 @@ const Comment = ({ commentItem }) => { /> @@ -297,13 +312,22 @@ const AccountProfile = ({ accountId }) => { const parseProposalKeyAndValue = (key, modifiedValue, originalValue) => { switch (key) { + case "terms_and_condition": { + return ( + + accepted + + + ); + } case "name": return changed title; case "summary": case "description": return changed {key}; - case "labels": - return changed labels to {(modifiedValue ?? []).join(", ")}; case "category": return ( @@ -421,7 +445,10 @@ const Log = ({ timestamp }) => { : "inline-flex") } > - + {parseProposalKeyAndValue(i.key, i.modifiedValue, i.originalValue)} diff --git a/instances/events-committee.near/widget/devhub/entity/proposal/ComposeComment.jsx b/instances/events-committee.near/widget/devhub/entity/proposal/ComposeComment.jsx index 40441bed5..7e8209392 100644 --- a/instances/events-committee.near/widget/devhub/entity/proposal/ComposeComment.jsx +++ b/instances/events-committee.near/widget/devhub/entity/proposal/ComposeComment.jsx @@ -151,9 +151,10 @@ function composeData() { notifications.push({ key: notifyAccountId, value: { - type: "devhub/reply", + type: "proposal/reply", item, proposal: proposalId, + widgetAccountId: "${REPL_DEVHUB}", }, }); } @@ -169,6 +170,7 @@ function composeData() { onCommit: () => { setCommentToast(true); setComment(""); + Storage.privateSet(draftKey, ""); setHandler("committed"); setTxnCreated(false); }, @@ -205,10 +207,11 @@ const Compose = useMemo(() => { embeddCSS: ComposeEmbeddCSS, handler: handler, showProposalIdAutoComplete: true, + sortedRelevantUsers: props.sortedRelevantUsers, }} /> ); -}, [draftComment, handler]); +}, [draftComment, handler, props.sortedRelevantUsers]); return (
diff --git a/instances/infrastructure-committee.near/widget/components/molecule/ComposeComment.jsx b/instances/infrastructure-committee.near/widget/components/molecule/ComposeComment.jsx index b69786402..80ee4d1ac 100644 --- a/instances/infrastructure-committee.near/widget/components/molecule/ComposeComment.jsx +++ b/instances/infrastructure-committee.near/widget/components/molecule/ComposeComment.jsx @@ -183,6 +183,7 @@ function composeData() { onCommit: () => { setCommentToast(true); setComment(""); + Storage.privateSet(draftKey, ""); setHandler("refreshEditor"); setTxnCreated(false); }, diff --git a/instances/infrastructure-committee.near/widget/components/proposals/CommentsAndLogs.jsx b/instances/infrastructure-committee.near/widget/components/proposals/CommentsAndLogs.jsx index ec45090cd..6bcc85ea7 100644 --- a/instances/infrastructure-committee.near/widget/components/proposals/CommentsAndLogs.jsx +++ b/instances/infrastructure-committee.near/widget/components/proposals/CommentsAndLogs.jsx @@ -1,15 +1,8 @@ -const { PROPOSAL_TIMELINE_STATUS, isNumber, getLinkUsingCurrentGateway } = - VM.require(`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/core.common`) || { - PROPOSAL_TIMELINE_STATUS: {}, - isNumber: () => {}, - getLinkUsingCurrentGateway: () => {}, - }; - -const { href } = VM.require(`${REPL_DEVHUB}/widget/core.lib.url`); -href || (href = () => {}); - +const { getLinkUsingCurrentGateway } = VM.require( + "${REPL_DEVHUB}/widget/core.lib.url" +) || { getLinkUsingCurrentGateway: () => {} }; const snapshotHistory = props.snapshotHistory; -const latestSnapshot = props.latestSnapshot; +const proposalId = props.id; const Wrapper = styled.div` position: relative; @@ -67,11 +60,10 @@ function getDifferentKeysWithValues(obj1, obj2) { if (key !== "editor_id" && obj2.hasOwnProperty(key)) { const value1 = obj1[key]; const value2 = obj2[key]; - if (Array.isArray(value1) && Array.isArray(value2)) { - const sortedValue1 = [...value1].sort(); - const sortedValue2 = [...value2].sort(); - return JSON.stringify(sortedValue1) !== JSON.stringify(sortedValue2); - } else if (typeof value1 === "object" && typeof value2 === "object") { + + if (typeof value1 === "object" && typeof value2 === "object") { + return JSON.stringify(value1) !== JSON.stringify(value2); + } else if (Array.isArray(value1) && Array.isArray(value2)) { return JSON.stringify(value1) !== JSON.stringify(value2); } else { return value1 !== value2; @@ -90,12 +82,13 @@ State.init({ data: null, socialComments: null, changedKeysListWithValues: null, + snapshotHistoryLength: 0, }); function sortTimelineAndComments() { const comments = Social.index("comment", props.item, { subscribe: true }); - if (state.changedKeysListWithValues === null) { + if (snapshotHistory.length > state.snapshotHistoryLength) { const changedKeysListWithValues = snapshotHistory .slice(1) .map((item, index) => { @@ -105,7 +98,26 @@ function sortTimelineAndComments() { ...getDifferentKeysWithValues(startingPoint, item), }; }); - State.update({ changedKeysListWithValues }); + + // add log for accepting terms and condition + changedKeysListWithValues.unshift({ + 0: { + key: "timestamp", + originalValue: "0", + modifiedValue: snapshotHistory[0].timestamp, + }, + 1: { + key: "terms_and_condition", + originalValue: "", + modifiedValue: "accepted", + }, + editorId: snapshotHistory[0].editor_id, + }); + + State.update({ + changedKeysListWithValues, + snapshotHistoryLength: snapshotHistory.length, + }); } // sort comments and timeline logs by time @@ -138,10 +150,7 @@ function sortTimelineAndComments() { }); } -if ((snapshotHistory ?? []).length > 0) { - sortTimelineAndComments(); -} - +sortTimelineAndComments(); const Comment = ({ commentItem }) => { const { accountId, blockHeight } = commentItem; const item = { @@ -150,9 +159,7 @@ const Comment = ({ commentItem }) => { blockHeight, }; const content = JSON.parse(Social.get(item.path, blockHeight) ?? "null"); - const link = getLinkUsingCurrentGateway( - `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/app?page=proposal&id=${props.id}&accountId=${accountId}&blockHeight=${blockHeight}` - ); + const link = `https://${REPL_DEVHUB}.page/proposal/${proposalId}?accountId=${accountId}&blockHeight=${blockHeight}`; const hightlightComment = parseInt(props.blockHeight ?? "") === blockHeight && props.accountId === accountId; @@ -162,13 +169,14 @@ const Comment = ({ commentItem }) => {
@@ -179,7 +187,7 @@ const Comment = ({ commentItem }) => { commented ・{" "} { {context.accountId && (
{
{
- moved proposal to{" "} + moved proposal from{" "} + to{" "} + - ・ this proposal is selected for RFP{" "} - - #{latestSnapshot.linked_rfp} - + stage - ); - } else - return ( - oldValue !== newValue && ( - - moved proposal from{" "} - - to{" "} - - stage - - ) - ); - } + ) + ); case "sponsor_requested_review": return !oldValue && newValue && completed review; case "reviewer_completed_attestation": @@ -299,6 +287,9 @@ function parseTimelineKeyAndValue(timeline, originalValue, modifiedValue) { ); case "payouts": return updated the funding payment links.; + // we don't have this step for now + // case "request_for_trustees_created": + // return !oldValue && newValue && successfully created request for trustees; default: return null; } @@ -308,7 +299,7 @@ const AccountProfile = ({ accountId }) => { return ( { ); }; - const LinkToRfp = ({ id, children }) => { return ( { const parseProposalKeyAndValue = (key, modifiedValue, originalValue) => { switch (key) { + case "terms_and_condition": { + return ( + + accepted + + + ); + } case "name": return changed title; case "summary": @@ -347,6 +348,7 @@ const parseProposalKeyAndValue = (key, modifiedValue, originalValue) => { return changed {key}; case "labels": return changed labels to {(modifiedValue ?? []).join(", ")}; + case "category": return ( @@ -399,6 +401,13 @@ const parseProposalKeyAndValue = (key, modifiedValue, originalValue) => { to ); + case "requested_sponsor": + return ( + + changed sponsor from + to + + ); case "timeline": { const modifiedKeys = Object.keys(modifiedValue); const originalKeys = Object.keys(originalValue); @@ -448,7 +457,7 @@ const Log = ({ timestamp }) => { } return valuesArray.map((i, index) => { - if (i.key && i.key !== "timestamp") { + if (i.key && i.key !== "timestamp" && i.key !== "proposal_body_version") { return ( { : "inline-flex") } > - - {" "} + + {parseProposalKeyAndValue(i.key, i.modifiedValue, i.originalValue)} {i.key !== "timeline" && "・"} { test.use({ contextOptions: { @@ -15,10 +22,15 @@ test.describe("Don't ask again enabled", () => { storageState: "playwright-tests/storage-states/wallet-connected-with-devhub-access-key.json", }); - test("should add comment to a proposal", async ({ page }) => { - await page.goto("/devhub.near/widget/app?page=proposal&id=17"); + test("should add comment on a proposal", async ({ + page, + account: instanceAccount, + }) => { + await page.goto(`/${instanceAccount}/widget/app?page=proposal&id=5`); const widgetSrc = - "devhub.near/widget/devhub.entity.proposal.ComposeComment"; + instanceAccount === "infrastructure-committee.near" + ? "infrastructure-committee.near/widget/components.molecule.ComposeComment" + : `${instanceAccount}/widget/devhub.entity.proposal.ComposeComment`; const delay_milliseconds_between_keypress_when_typing = 0; const commentArea = await page @@ -32,11 +44,11 @@ test.describe("Don't ask again enabled", () => { await commentArea.blur(); await pauseIfVideoRecording(page); - const account = "petersalomonsen.near"; + const accountId = "petersalomonsen.near"; await setCommitWritePermissionDontAskAgainCacheValues({ page, widgetSrc, - accountId: account, + accountId: accountId, }); await mockTransactionSubmitRPCResponses( @@ -49,12 +61,12 @@ test.describe("Don't ask again enabled", () => { if ( postData.params.account_id === "social.near" && postData.params.method_name === "get" && - args === `{"keys":["${account}/post/**"]}` + args === `{"keys":["${accountId}/post/**"]}` ) { const response = await route.fetch(); const json = await response.json(); const resultObj = decodeResultJSON(json.result.result); - resultObj[account].post.main = JSON.stringify({ + resultObj[accountId].post.main = JSON.stringify({ text: text, }); json.result.result = encodeResultJSON(resultObj); @@ -75,6 +87,7 @@ test.describe("Don't ask again enabled", () => { await expect( await page.frameLocator("iframe").locator(".CodeMirror") ).toContainText(text); + const loadingIndicator = await page.locator(".comment-btn-spinner"); await expect(loadingIndicator).toBeAttached(); await loadingIndicator.waitFor({ state: "detached", timeout: 30000 }); @@ -96,11 +109,14 @@ test.describe("Don't ask again enabled", () => { }); test("should paste a long comment to a proposal, see that the comment appears after submission, and that the comment field is cleared, even after reloading the page", async ({ page, + account: instanceAccount, }) => { test.setTimeout(2 * 60000); - await page.goto("/devhub.near/widget/app?page=proposal&id=17"); + await page.goto(`/${instanceAccount}/widget/app?page=proposal&id=5`); const widgetSrc = - "devhub.near/widget/devhub.entity.proposal.ComposeComment"; + instanceAccount === "infrastructure-committee.near" + ? "infrastructure-committee.near/widget/components.molecule.ComposeComment" + : `${instanceAccount}/widget/devhub.entity.proposal.ComposeComment`; let commentButton = await page.getByRole("button", { name: "Comment" }); await expect(commentButton).toBeAttached({ timeout: 10000 }); @@ -136,11 +152,11 @@ test.describe("Don't ask again enabled", () => { await commentArea.blur(); await pauseIfVideoRecording(page); - const account = "petersalomonsen.near"; + const userAccount = "petersalomonsen.near"; await setCommitWritePermissionDontAskAgainCacheValues({ page, widgetSrc, - accountId: account, + accountId: userAccount, }); const transactionMockStatus = await mockTransactionSubmitRPCResponses( @@ -154,13 +170,13 @@ test.describe("Don't ask again enabled", () => { if ( postData.params.account_id === "social.near" && postData.params.method_name === "get" && - args === `{"keys":["${account}/post/**"]}` + args === `{"keys":["${userAccount}/post/**"]}` ) { const response = await route.fetch(); const json = await response.json(); const resultObj = decodeResultJSON(json.result.result); - resultObj[account].post.main = JSON.stringify({ + resultObj[userAccount].post.main = JSON.stringify({ text: commentText, }); json.result.result = encodeResultJSON(resultObj); diff --git a/playwright.config.js b/playwright.config.js index ddef51b1f..bbc770de8 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -61,14 +61,15 @@ export default defineConfig({ /* Configure projects for major browsers */ projects: [ { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, + name: "infrastructure", + use: { + ...devices["Desktop Chrome"], + account: "infrastructure-committee.near", + }, }, - { name: "infrastructure", use: { ...devices["Desktop Chrome"] } }, - { name: "events", - testMatch: /events\/.*.spec.js/, // (proposal|events) + testMatch: /(events|proposal)\/.*.spec.js/, // (proposal|events) use: { ...devices["Desktop Chrome"], baseURL: "http://localhost:8080", From 6d879bb89af2f580f6d4df2c750eb46d7eff95cf Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Wed, 17 Jul 2024 08:35:49 +0000 Subject: [PATCH 8/9] fix so that it's passing for devhub after changing proposal id from 17 to 5 --- playwright-tests/tests/proposal/comment.spec.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/playwright-tests/tests/proposal/comment.spec.js b/playwright-tests/tests/proposal/comment.spec.js index 557ecdd96..6566fae40 100644 --- a/playwright-tests/tests/proposal/comment.spec.js +++ b/playwright-tests/tests/proposal/comment.spec.js @@ -206,7 +206,7 @@ test.describe("Don't ask again enabled", () => { const transactionDataJsonStartIndex = lastTransactionParamBuffer.indexOf('{"data":'); const transactionDataJsonEndIndex = - lastTransactionParamBuffer.indexOf('}}]"}}}}') + '}}]"}}}}'.length; + lastTransactionParamBuffer.indexOf('"}}}}') + '"}}}}'.length; const transactionDataJsonString = lastTransactionParamBuffer.subarray( transactionDataJsonStartIndex, transactionDataJsonEndIndex @@ -245,7 +245,9 @@ test.describe("Don't ask again enabled", () => { ); await expect(transaction_successful_toast).toBeVisible(); - await expect(transaction_successful_toast).not.toBeAttached(); + await expect(transaction_successful_toast).not.toBeAttached({ + timeout: 10000, + }); await expect( await page.frameLocator("iframe").locator(".CodeMirror") ).not.toContainText(commentText); @@ -263,7 +265,8 @@ test.describe("Don't ask again enabled", () => { await expect(commentElement).toBeVisible({ timeout: 10000 }); await commentElement.scrollIntoViewIfNeeded(); await expect(commentElement).toContainText( - "Typically, funds are disbursed within 10 business days, but the timeline can vary depending on the project's complexity and paperwork. Your DevDAO Moderator will keep you updated." + "Typically, funds are disbursed within 10 business days, but the timeline can vary depending on the project's complexity and paperwork. Your DevDAO Moderator will keep you updated.", + { timeout: 10000 } ); await page.reload(); @@ -272,7 +275,8 @@ test.describe("Don't ask again enabled", () => { await expect(commentElement).toBeVisible({ timeout: 20000 }); await commentElement.scrollIntoViewIfNeeded(); await expect(commentElement).toContainText( - "Typically, funds are disbursed within 10 business days, but the timeline can vary depending on the project's complexity and paperwork. Your DevDAO Moderator will keep you updated." + "Typically, funds are disbursed within 10 business days, but the timeline can vary depending on the project's complexity and paperwork. Your DevDAO Moderator will keep you updated.", + { timeout: 10000 } ); commentButton = await page.getByRole("button", { name: "Comment" }); From 1830c8eaab162a6612f0b5dbbe1315e8ed62b5fd Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Wed, 17 Jul 2024 09:22:06 +0000 Subject: [PATCH 9/9] apply draft clearing fix for infrastructure committee event though the sources for IC have diverged from devhub, running the same test suite ensures that the comment draft clearing functionality pass the same test criterias --- .../continuous-integration-workflow-infra.yml | 19 ++- .../components/molecule/ComposeComment.jsx | 2 +- .../widget/components/molecule/SimpleMDE.jsx | 2 +- .../components/proposals/CommentsAndLogs.jsx | 156 ++++++++---------- 4 files changed, 92 insertions(+), 87 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow-infra.yml b/.github/workflows/continuous-integration-workflow-infra.yml index 652d2812a..446b60122 100644 --- a/.github/workflows/continuous-integration-workflow-infra.yml +++ b/.github/workflows/continuous-integration-workflow-infra.yml @@ -19,7 +19,24 @@ jobs: run: npm ci - name: Run code formatting check run: npm run fmt:check - + playwright-tests-proposal: + name: Proposal - Playwright tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "npm" + - name: Install dependencies + run: | + npm ci + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/mpeterdev/bos-loader/releases/download/v0.7.1/bos-loader-v0.7.1-installer.sh | sh + npx playwright install-deps + npx playwright install + - name: Run tests + run: | + INSTANCE=infrastructure npx playwright test --project=infrastructure playwright-tests/tests/proposal/comment.spec.js playwright-tests-infra: name: Infrastructure Committee - Playwright tests runs-on: ubuntu-latest diff --git a/instances/infrastructure-committee.near/widget/components/molecule/ComposeComment.jsx b/instances/infrastructure-committee.near/widget/components/molecule/ComposeComment.jsx index 80ee4d1ac..22ab6bb6b 100644 --- a/instances/infrastructure-committee.near/widget/components/molecule/ComposeComment.jsx +++ b/instances/infrastructure-committee.near/widget/components/molecule/ComposeComment.jsx @@ -184,7 +184,7 @@ function composeData() { setCommentToast(true); setComment(""); Storage.privateSet(draftKey, ""); - setHandler("refreshEditor"); + setHandler("committed"); setTxnCreated(false); }, onCancel: () => { diff --git a/instances/infrastructure-committee.near/widget/components/molecule/SimpleMDE.jsx b/instances/infrastructure-committee.near/widget/components/molecule/SimpleMDE.jsx index a2157e055..9fd0bb122 100644 --- a/instances/infrastructure-committee.near/widget/components/molecule/SimpleMDE.jsx +++ b/instances/infrastructure-committee.near/widget/components/molecule/SimpleMDE.jsx @@ -548,7 +548,7 @@ window.addEventListener("message", (event) => { simplemde.value(event.data.content); isEditorInitialized = true; } else { - if (event.data.handler === 'refreshEditor') { + if (event.data.handler === 'refreshEditor' || event.data.handler === 'committed') { codeMirrorInstance.getDoc().setValue(event.data.content); } } diff --git a/instances/infrastructure-committee.near/widget/components/proposals/CommentsAndLogs.jsx b/instances/infrastructure-committee.near/widget/components/proposals/CommentsAndLogs.jsx index 6bcc85ea7..ec62b4e1f 100644 --- a/instances/infrastructure-committee.near/widget/components/proposals/CommentsAndLogs.jsx +++ b/instances/infrastructure-committee.near/widget/components/proposals/CommentsAndLogs.jsx @@ -1,8 +1,15 @@ -const { getLinkUsingCurrentGateway } = VM.require( - "${REPL_DEVHUB}/widget/core.lib.url" -) || { getLinkUsingCurrentGateway: () => {} }; +const { PROPOSAL_TIMELINE_STATUS, isNumber, getLinkUsingCurrentGateway } = + VM.require(`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/core.common`) || { + PROPOSAL_TIMELINE_STATUS: {}, + isNumber: () => {}, + getLinkUsingCurrentGateway: () => {}, + }; + +const { href } = VM.require(`${REPL_DEVHUB}/widget/core.lib.url`); +href || (href = () => {}); + const snapshotHistory = props.snapshotHistory; -const proposalId = props.id; +const latestSnapshot = props.latestSnapshot; const Wrapper = styled.div` position: relative; @@ -60,10 +67,11 @@ function getDifferentKeysWithValues(obj1, obj2) { if (key !== "editor_id" && obj2.hasOwnProperty(key)) { const value1 = obj1[key]; const value2 = obj2[key]; - - if (typeof value1 === "object" && typeof value2 === "object") { - return JSON.stringify(value1) !== JSON.stringify(value2); - } else if (Array.isArray(value1) && Array.isArray(value2)) { + if (Array.isArray(value1) && Array.isArray(value2)) { + const sortedValue1 = [...value1].sort(); + const sortedValue2 = [...value2].sort(); + return JSON.stringify(sortedValue1) !== JSON.stringify(sortedValue2); + } else if (typeof value1 === "object" && typeof value2 === "object") { return JSON.stringify(value1) !== JSON.stringify(value2); } else { return value1 !== value2; @@ -82,13 +90,12 @@ State.init({ data: null, socialComments: null, changedKeysListWithValues: null, - snapshotHistoryLength: 0, }); function sortTimelineAndComments() { const comments = Social.index("comment", props.item, { subscribe: true }); - if (snapshotHistory.length > state.snapshotHistoryLength) { + if (state.changedKeysListWithValues === null) { const changedKeysListWithValues = snapshotHistory .slice(1) .map((item, index) => { @@ -98,26 +105,7 @@ function sortTimelineAndComments() { ...getDifferentKeysWithValues(startingPoint, item), }; }); - - // add log for accepting terms and condition - changedKeysListWithValues.unshift({ - 0: { - key: "timestamp", - originalValue: "0", - modifiedValue: snapshotHistory[0].timestamp, - }, - 1: { - key: "terms_and_condition", - originalValue: "", - modifiedValue: "accepted", - }, - editorId: snapshotHistory[0].editor_id, - }); - - State.update({ - changedKeysListWithValues, - snapshotHistoryLength: snapshotHistory.length, - }); + State.update({ changedKeysListWithValues }); } // sort comments and timeline logs by time @@ -150,7 +138,10 @@ function sortTimelineAndComments() { }); } -sortTimelineAndComments(); +if ((snapshotHistory ?? []).length > 0) { + sortTimelineAndComments(); +} + const Comment = ({ commentItem }) => { const { accountId, blockHeight } = commentItem; const item = { @@ -160,6 +151,7 @@ const Comment = ({ commentItem }) => { }; const content = JSON.parse(Social.get(item.path, blockHeight) ?? "null"); const link = `https://${REPL_DEVHUB}.page/proposal/${proposalId}?accountId=${accountId}&blockHeight=${blockHeight}`; + const hightlightComment = parseInt(props.blockHeight ?? "") === blockHeight && props.accountId === accountId; @@ -169,7 +161,7 @@ const Comment = ({ commentItem }) => {
{ commented ・{" "} { {context.accountId && (
{
{
- moved proposal from{" "} + moved proposal to{" "} - to{" "} - - stage + ・ this proposal is selected for RFP{" "} + + #{latestSnapshot.linked_rfp} + - ) - ); + ); + } else + return ( + oldValue !== newValue && ( + + moved proposal from{" "} + + to{" "} + + stage + + ) + ); + } case "sponsor_requested_review": return !oldValue && newValue && completed review; case "reviewer_completed_attestation": @@ -287,9 +299,6 @@ function parseTimelineKeyAndValue(timeline, originalValue, modifiedValue) { ); case "payouts": return updated the funding payment links.; - // we don't have this step for now - // case "request_for_trustees_created": - // return !oldValue && newValue && successfully created request for trustees; default: return null; } @@ -299,7 +308,7 @@ const AccountProfile = ({ accountId }) => { return ( { ); }; + const LinkToRfp = ({ id, children }) => { return ( { const parseProposalKeyAndValue = (key, modifiedValue, originalValue) => { switch (key) { - case "terms_and_condition": { - return ( - - accepted - - - ); - } case "name": return changed title; case "summary": @@ -348,7 +347,6 @@ const parseProposalKeyAndValue = (key, modifiedValue, originalValue) => { return changed {key}; case "labels": return changed labels to {(modifiedValue ?? []).join(", ")}; - case "category": return ( @@ -401,13 +399,6 @@ const parseProposalKeyAndValue = (key, modifiedValue, originalValue) => { to ); - case "requested_sponsor": - return ( - - changed sponsor from - to - - ); case "timeline": { const modifiedKeys = Object.keys(modifiedValue); const originalKeys = Object.keys(originalValue); @@ -457,7 +448,7 @@ const Log = ({ timestamp }) => { } return valuesArray.map((i, index) => { - if (i.key && i.key !== "timestamp" && i.key !== "proposal_body_version") { + if (i.key && i.key !== "timestamp") { return ( { : "inline-flex") } > - - + + {" "} {parseProposalKeyAndValue(i.key, i.modifiedValue, i.originalValue)} {i.key !== "timeline" && "・"}