diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c28d95c9..1f2cc1cec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ changes. ### Fixed +- Fix blank page on dRep details when link or identity references contain objects: { @value: ... } not strings [Issue 3733](https://github.com/IntersectMBO/govtool/issues/3733) - Fix missing off chain references in DRep details [Issue 3490](https://github.com/IntersectMBO/govtool/issues/3490) - Fix blank screen and type error on linkReferences when navigating to edit dRep page that has no links [Issue 3714](https://github.com/IntersectMBO/govtool/issues/3714) - Fix adding two link input fields when editing the dRep form when no links are present initially [Issue 3709](https://github.com/IntersectMBO/govtool/issues/3709) diff --git a/govtool/backend/sql/list-dreps.sql b/govtool/backend/sql/list-dreps.sql index 3062c9f48..1ae00d6fa 100644 --- a/govtool/backend/sql/list-dreps.sql +++ b/govtool/backend/sql/list-dreps.sql @@ -95,7 +95,7 @@ HasNonDeregisterVotingAnchor AS ( EXISTS ( SELECT 1 FROM drep_registration dr_sub - WHERE + WHERE dr_sub.drep_hash_id = dr.drep_hash_id AND dr_sub.voting_anchor_id IS NULL AND COALESCE(dr_sub.deposit, 0) >= 0 @@ -129,12 +129,24 @@ DRepData AS ( off_chain_vote_drep_data.image_hash, COALESCE( ( - SELECT jsonb_agg(ref) + SELECT jsonb_agg( + jsonb_build_object( + 'uri', COALESCE( + CASE WHEN jsonb_typeof(ref->'uri') = 'string' THEN ref->>'uri' END, + ref->'uri'->>'@value' + ), + '@type', ref->>'@type', + 'label', COALESCE( + CASE WHEN jsonb_typeof(ref->'label') = 'string' THEN ref->>'label' END, + ref->'label'->>'@value' + ) + ) + ) FROM jsonb_array_elements( - CASE - WHEN (ocvd.json::jsonb)->'body'->'references' IS NOT NULL - THEN (ocvd.json::jsonb)->'body'->'references' - ELSE '[]'::jsonb + CASE + WHEN (ocvd.json::jsonb)->'body'->'references' IS NOT NULL + THEN (ocvd.json::jsonb)->'body'->'references' + ELSE '[]'::jsonb END ) AS ref WHERE ref->>'@type' = 'Identity' @@ -143,12 +155,24 @@ DRepData AS ( ) AS identity_references, COALESCE( ( - SELECT jsonb_agg(ref) + SELECT jsonb_agg( + jsonb_build_object( + 'uri', COALESCE( + CASE WHEN jsonb_typeof(ref->'uri') = 'string' THEN ref->>'uri' END, + ref->'uri'->>'@value' + ), + '@type', ref->>'@type', + 'label', COALESCE( + CASE WHEN jsonb_typeof(ref->'label') = 'string' THEN ref->>'label' END, + ref->'label'->>'@value' + ) + ) + ) FROM jsonb_array_elements( - CASE - WHEN (ocvd.json::jsonb)->'body'->'references' IS NOT NULL - THEN (ocvd.json::jsonb)->'body'->'references' - ELSE '[]'::jsonb + CASE + WHEN (ocvd.json::jsonb)->'body'->'references' IS NOT NULL + THEN (ocvd.json::jsonb)->'body'->'references' + ELSE '[]'::jsonb END ) AS ref WHERE ref->>'@type' = 'Link' @@ -185,7 +209,7 @@ DRepData AS ( LEFT JOIN FetchError fetch_error ON fetch_error.voting_anchor_id = leva.voting_anchor_id LEFT JOIN HasNonDeregisterVotingAnchor hndva ON hndva.drep_hash_id = dh.id LEFT JOIN off_chain_vote_data ocvd ON ocvd.voting_anchor_id = leva.voting_anchor_id - LEFT JOIN off_chain_vote_drep_data ON off_chain_vote_drep_data.off_chain_vote_data_id = ocvd.id + LEFT JOIN off_chain_vote_drep_data ON off_chain_vote_drep_data.off_chain_vote_data_id = ocvd.id LEFT JOIN voting_procedure ON voting_procedure.drep_voter = dh.id LEFT JOIN tx voting_procedure_transaction ON voting_procedure_transaction.id = voting_procedure.tx_id LEFT JOIN block voting_procedure_block ON voting_procedure_block.id = voting_procedure_transaction.block_id @@ -242,23 +266,47 @@ DRepData AS ( off_chain_vote_drep_data.image_url, off_chain_vote_drep_data.image_hash, ( - SELECT jsonb_agg(ref) + SELECT jsonb_agg( + jsonb_build_object( + 'uri', COALESCE( + CASE WHEN jsonb_typeof(ref->'uri') = 'string' THEN ref->>'uri' END, + ref->'uri'->>'@value' + ), + '@type', ref->>'@type', + 'label', COALESCE( + CASE WHEN jsonb_typeof(ref->'label') = 'string' THEN ref->>'label' END, + ref->'label'->>'@value' + ) + ) + ) FROM jsonb_array_elements( - CASE - WHEN (ocvd.json::jsonb)->'body'->'references' IS NOT NULL - THEN (ocvd.json::jsonb)->'body'->'references' - ELSE '[]'::jsonb + CASE + WHEN (ocvd.json::jsonb)->'body'->'references' IS NOT NULL + THEN (ocvd.json::jsonb)->'body'->'references' + ELSE '[]'::jsonb END ) AS ref WHERE ref->>'@type' = 'Identity' ), ( - SELECT jsonb_agg(ref) + SELECT jsonb_agg( + jsonb_build_object( + 'uri', COALESCE( + CASE WHEN jsonb_typeof(ref->'uri') = 'string' THEN ref->>'uri' END, + ref->'uri'->>'@value' + ), + '@type', ref->>'@type', + 'label', COALESCE( + CASE WHEN jsonb_typeof(ref->'label') = 'string' THEN ref->>'label' END, + ref->'label'->>'@value' + ) + ) + ) FROM jsonb_array_elements( - CASE - WHEN (ocvd.json::jsonb)->'body'->'references' IS NOT NULL - THEN (ocvd.json::jsonb)->'body'->'references' - ELSE '[]'::jsonb + CASE + WHEN (ocvd.json::jsonb)->'body'->'references' IS NOT NULL + THEN (ocvd.json::jsonb)->'body'->'references' + ELSE '[]'::jsonb END ) AS ref WHERE ref->>'@type' = 'Link' @@ -275,4 +323,4 @@ WHERE objectives ILIKE ? OR motivations ILIKE ? OR qualifications ILIKE ? - ) \ No newline at end of file + ) diff --git a/tests/govtool-frontend/playwright/lib/forms/dRepForm.ts b/tests/govtool-frontend/playwright/lib/forms/dRepForm.ts index b036726d8..91358f9f9 100644 --- a/tests/govtool-frontend/playwright/lib/forms/dRepForm.ts +++ b/tests/govtool-frontend/playwright/lib/forms/dRepForm.ts @@ -166,6 +166,8 @@ export default class DRepForm { await this.identityReferenceFirstDescriptionInput.fill( dRepInfo.identityReferenceLinks[0].description ); + + await this.form.keyboard.press("Tab"); } async validateForm(dRepInfo: IDRepInfo) { diff --git a/tests/govtool-frontend/playwright/lib/helpers/transaction.ts b/tests/govtool-frontend/playwright/lib/helpers/transaction.ts index 1cd3e914c..ce22f0278 100644 --- a/tests/govtool-frontend/playwright/lib/helpers/transaction.ts +++ b/tests/govtool-frontend/playwright/lib/helpers/transaction.ts @@ -77,7 +77,7 @@ export async function waitForTxConfirmation( .getByTestId("alert-warning") .getByText("Transaction in progress", { exact: false }) ).toBeVisible({ - timeout: 60_000, + timeout: 90_000, }); const url = (await transactionStatusPromise).url(); const regex = /\/transaction\/status\/([^\/]+)$/; @@ -90,7 +90,7 @@ export async function waitForTxConfirmation( await pollTransaction(transactionHash); await expect( page.getByText("In Progress", { exact: true }).first() //FIXME: Only one element needs to be displayed - ).not.toBeVisible({ timeout: 60_000 }); + ).not.toBeVisible({ timeout: 90_000 }); } } catch (error) { Logger.fail(error.message); diff --git a/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionPage.ts b/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionPage.ts index 571d0688a..76b1187b0 100644 --- a/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionPage.ts @@ -174,7 +174,11 @@ export default class BudgetDiscussionPage { // API validation for (let i = 0; i <= proposals.length - 2; i++) { const isValid = validationFn(proposals[i], proposals[i + 1]); - expect(isValid).toBe(true); + expect(isValid, { + message: + !isValid && + `Failed on sorting ${type} with proposals: ${proposals[i].id} and ${proposals[i + 1].id}`, + }).toBe(true); } } diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts index c2c301c39..231ee041a 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts @@ -183,7 +183,11 @@ export default class ProposalDiscussionPage { // API validation for (let i = 0; i <= proposals.length - 2; i++) { const isValid = validationFn(proposals[i], proposals[i + 1]); - expect(isValid).toBe(true); + expect(isValid, { + message: + !isValid && + `Failed on sorting ${type} with proposals: ${proposals[i].id} and ${proposals[i + 1].id}`, + }).toBe(true); } } diff --git a/tests/govtool-frontend/playwright/lib/types.ts b/tests/govtool-frontend/playwright/lib/types.ts index 2ffdac42c..8f223240d 100644 --- a/tests/govtool-frontend/playwright/lib/types.ts +++ b/tests/govtool-frontend/playwright/lib/types.ts @@ -210,6 +210,7 @@ export type ProposedGovAction = { attributes: { proposal_id: string; prop_name: string; + createdAt: string; }; }; creator: { diff --git a/tests/govtool-frontend/playwright/tests/11-proposal-budget/proposalBudget.spec.ts b/tests/govtool-frontend/playwright/tests/11-proposal-budget/proposalBudget.spec.ts index 4cf3b5fb3..81954b640 100644 --- a/tests/govtool-frontend/playwright/tests/11-proposal-budget/proposalBudget.spec.ts +++ b/tests/govtool-frontend/playwright/tests/11-proposal-budget/proposalBudget.spec.ts @@ -108,6 +108,7 @@ test.describe("Budget proposal list manipulation", () => { }); test("11B_3. Should sort budget proposals", async () => { + test.slow(); const sortOptions = { Oldest: (p1: ProposedGovAction, p2: ProposedGovAction) => p1.attributes.createdAt <= p2.attributes.createdAt, @@ -120,21 +121,35 @@ test.describe("Budget proposal list manipulation", () => { p1.attributes.prop_comments_number <= p2.attributes.prop_comments_number, "Name A-Z": (p1: ProposedGovAction, p2: ProposedGovAction) => - p1.attributes.bd_proposal_detail.data.attributes.proposal_name.localeCompare( - p2.attributes.bd_proposal_detail.data.attributes.proposal_name - ) <= 0, + p1.attributes.bd_proposal_detail.data.attributes.proposal_name + .replace(/ /g, "") + .localeCompare( + p2.attributes.bd_proposal_detail.data.attributes.proposal_name.replace( + / /g, + "" + ) + ) <= 0, "Name Z-A": (p1: ProposedGovAction, p2: ProposedGovAction) => - p1.attributes.bd_proposal_detail.data.attributes.proposal_name.localeCompare( - p2.attributes.bd_proposal_detail.data.attributes.proposal_name - ) >= 0, + p1.attributes.bd_proposal_detail.data.attributes.proposal_name + .replace(/ /g, "") + .localeCompare( + p2.attributes.bd_proposal_detail.data.attributes.proposal_name.replace( + / /g, + "" + ) + ) >= 0, "Proposer A-Z": (p1: ProposedGovAction, p2: ProposedGovAction) => - p1.attributes.creator.data.attributes.govtool_username.localeCompare( - p2.attributes.creator.data.attributes.govtool_username - ) <= 0, + p1.attributes.creator.data.attributes.govtool_username + .replace(/ /g, "") + .localeCompare( + p2.attributes.creator.data.attributes.govtool_username + ) <= 0, "Proposer Z-A": (p1: ProposedGovAction, p2: ProposedGovAction) => - p1.attributes.creator.data.attributes.govtool_username.localeCompare( - p2.attributes.creator.data.attributes.govtool_username - ) >= 0, + p1.attributes.creator.data.attributes.govtool_username + .replace(/ /g, "") + .localeCompare( + p2.attributes.creator.data.attributes.govtool_username + ) >= 0, }; for (const [option, validationFn] of Object.entries(sortOptions)) { diff --git a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.spec.ts b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.spec.ts index 0b3395cbb..c382fe6bf 100644 --- a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.spec.ts +++ b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.spec.ts @@ -203,6 +203,7 @@ test("4H. Should verify none of the displayed governance actions have expired", async () => { const proposalCards = await govActionsPage.getAllProposals(); for (const proposalCard of proposalCards) { + await expect(proposalCard).toBeVisible(); const expiryDateEl = proposalCard.getByTestId("expiry-date"); const expiryDateTxt = await expiryDateEl.innerText(); const expiryDate = extractExpiryDateFromText(expiryDateTxt); diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts index a895ecfa5..fbd725fec 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts @@ -69,11 +69,14 @@ test.describe("Filter and sort proposals", () => { }); test("8B_2. Should sort the list of proposed governance actions.", async () => { + test.slow(); const sortOptions = { Oldest: (p1: ProposedGovAction, p2: ProposedGovAction) => - p1.attributes.createdAt <= p2.attributes.createdAt, + p1.attributes.content.attributes.createdAt <= + p2.attributes.content.attributes.createdAt, Newest: (p1: ProposedGovAction, p2: ProposedGovAction) => - p1.attributes.createdAt >= p2.attributes.createdAt, + p1.attributes.content.attributes.createdAt >= + p2.attributes.content.attributes.createdAt, "Most likes": (p1: ProposedGovAction, p2: ProposedGovAction) => p1.attributes.prop_likes >= p2.attributes.prop_likes, "Least likes": (p1: ProposedGovAction, p2: ProposedGovAction) => @@ -89,13 +92,17 @@ test.describe("Filter and sort proposals", () => { p1.attributes.prop_comments_number <= p2.attributes.prop_comments_number, "Name A-Z": (p1: ProposedGovAction, p2: ProposedGovAction) => - p1.attributes.content.attributes.prop_name.localeCompare( - p2.attributes.content.attributes.prop_name - ) <= 0, + p1.attributes.content.attributes.prop_name + .replace(/ /g, "") + .localeCompare( + p2.attributes.content.attributes.prop_name.replace(/ /g, "") + ) <= 0, "Name Z-A": (p1: ProposedGovAction, p2: ProposedGovAction) => - p1.attributes.content.attributes.prop_name.localeCompare( - p2.attributes.content.attributes.prop_name - ) >= 0, + p1.attributes.content.attributes.prop_name + .replace(/ /g, "") + .localeCompare( + p2.attributes.content.attributes.prop_name.replace(/ /g, "") + ) >= 0, }; for (const [sortOption, sortFunction] of Object.entries(sortOptions)) { @@ -149,7 +156,9 @@ test("8C. Should search the list of proposed governance actions.", async ({ const proposalTitle = await proposalCard .locator('[data-testid^="proposal-"][data-testid$="-title"]') .innerText(); - expect(proposalTitle.trim()).toContain(proposalName.trim()); + expect(proposalTitle.toLowerCase().trim()).toContain( + proposalName.toLowerCase().trim() + ); } }, {