diff --git a/apps/explorer/src/__mocks__/react-markdown.js b/apps/explorer/src/__mocks__/react-markdown.js
new file mode 100644
index 000000000..63586cfef
--- /dev/null
+++ b/apps/explorer/src/__mocks__/react-markdown.js
@@ -0,0 +1,5 @@
+function ReactMarkdown({ children }) {
+ return <>{children}>;
+}
+
+export default ReactMarkdown;
diff --git a/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-error.spec.tsx b/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-error.spec.tsx
new file mode 100644
index 000000000..b2140c6d5
--- /dev/null
+++ b/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-error.spec.tsx
@@ -0,0 +1,67 @@
+import type { ApolloError } from '@apollo/client';
+import { MockedProvider } from '@apollo/client/testing';
+import { render } from '@testing-library/react';
+import { AssetStatus } from '@vegaprotocol/types';
+import { MemoryRouter } from 'react-router-dom';
+import { BundleError } from './bundle-error';
+
+describe('Bundle Error', () => {
+ const NON_ENABLED_STATUS: AssetStatus[] = [
+ AssetStatus.STATUS_PENDING_LISTING,
+ AssetStatus.STATUS_PROPOSED,
+ AssetStatus.STATUS_REJECTED,
+ ];
+
+ const ENABLED_STATUS: AssetStatus[] = [AssetStatus.STATUS_ENABLED];
+
+ it.each(NON_ENABLED_STATUS)(
+ 'shows the apollo error if not enabled and a message is provided',
+ (status) => {
+ const screen = render(
+
+
+
+
+
+ );
+
+ expect(screen.getByText('test-error-message')).toBeInTheDocument();
+ }
+ );
+
+ it.each(NON_ENABLED_STATUS)(
+ 'shows some fallback error message if the bundle has not been used and there is no error',
+ (status) => {
+ const screen = render(
+
+
+
+
+
+ );
+
+ expect(screen.getByText('No bundle for proposal ID')).toBeInTheDocument();
+ }
+ );
+
+ it.each(ENABLED_STATUS)(
+ 'hides ProposalLink if the status is enabled',
+ (status) => {
+ const screen = render(
+
+
+
+
+
+ );
+
+ expect(screen.getByText('Asset already enabled')).toBeInTheDocument();
+ }
+ );
+});
diff --git a/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-exists.spec.tsx b/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-exists.spec.tsx
new file mode 100644
index 000000000..ce4c7bd99
--- /dev/null
+++ b/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-exists.spec.tsx
@@ -0,0 +1,64 @@
+import { MockedProvider } from '@apollo/client/testing';
+import { render } from '@testing-library/react';
+import { AssetStatus } from '@vegaprotocol/types';
+import { MemoryRouter } from 'react-router-dom';
+import { BundleExists } from './bundle-exists';
+
+describe('Bundle Exists', () => {
+ const NON_ENABLED_STATUS: AssetStatus[] = [
+ AssetStatus.STATUS_PENDING_LISTING,
+ AssetStatus.STATUS_PROPOSED,
+ AssetStatus.STATUS_REJECTED,
+ ];
+
+ const ENABLED_STATUS: AssetStatus[] = [AssetStatus.STATUS_ENABLED];
+
+ const MOCK_SIGNATURES =
+ '0x1760ce4efec01d5bb9fe57c0660a39a27e0b5a1bd39b4d3fc0452bf142a4ef733baaf4dbedd74cd676c759f96ac7f412ec05e136bbff8a81d9f0c2428df8e099004c4d166ece0527d86e202386e8758580790d0a46f6429baaa268b4bf5c11c9e3402e175a9022ae8295e543853c01383ef17cd58458ddfc9c706fca0ecffa0d3d0160eb5234e63a78b9649428ca7659eb693fdde86edfc6c2707ef2e8fbf4c76a9f0eebe183da571a58837e2fa995d7b10955ecf04c138dc0ce964f17c3de1f5c6d0060ec132cc515cf974ead1da38e2ff8d4b870335865e8d4d8c982182bddea18bb513e2d37fd32de7fedc0c4f694e6bcdcf20f8547f19e7d9d25ce32c6ca9ea51e01ad3b92475778d9da7251d3943071f59107c9cd7ad9dd1923c06e88d3352869d919a591bf30732bad2c3fcf30a9f664dbb7a9c65a64875a48cbeb5741bd0a853901';
+ const MOCK_NONCE =
+ '18250011763873610289536200551900545467959221115607409799241178172533618346952';
+ const MOCK_PROPOSAL_ID =
+ '285923fed8c66ffb416b163e8ec72d3a87b9b8e2570e7ee7fe97d7092a918bc8';
+
+ const PROPOSAL_LINK_TEXT = 'Visit our Governance site to submit this';
+
+ it.each(NON_ENABLED_STATUS)(
+ 'shows a handy ProposalLink if the status is anything except enabled',
+ (status) => {
+ const screen = render(
+
+
+
+
+
+ );
+
+ expect(screen.getByText(PROPOSAL_LINK_TEXT)).toBeInTheDocument();
+ }
+ );
+
+ it.each(ENABLED_STATUS)(
+ 'hides ProposalLink if the status is enabled',
+ (status) => {
+ const screen = render(
+
+
+
+
+
+ );
+
+ expect(screen.queryAllByText(PROPOSAL_LINK_TEXT)).toEqual([]);
+ }
+ );
+});
diff --git a/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-icon.spec.tsx b/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-icon.spec.tsx
new file mode 100644
index 000000000..f43ec322d
--- /dev/null
+++ b/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-icon.spec.tsx
@@ -0,0 +1,33 @@
+import { render } from '@testing-library/react';
+import { AssetStatus } from '@vegaprotocol/types';
+import { IconForBundleStatus } from './bundle-icon';
+
+describe('Bundle status icon', () => {
+ const NON_ENABLED_STATUS: AssetStatus[] = [
+ AssetStatus.STATUS_PENDING_LISTING,
+ AssetStatus.STATUS_PROPOSED,
+ AssetStatus.STATUS_REJECTED,
+ ];
+
+ const ENABLED_STATUS: AssetStatus[] = [AssetStatus.STATUS_ENABLED];
+
+ it.each(NON_ENABLED_STATUS)(
+ 'show a sparkle icon if the bundle is unused',
+ (status) => {
+ const screen = render();
+ const i = screen.getByRole('img');
+ expect(i).toHaveAttribute('aria-label');
+ expect(i.getAttribute('aria-label')).toMatch(/clean/);
+ }
+ );
+
+ it.each(ENABLED_STATUS)(
+ 'shows a tick if the bundle is already used',
+ (status) => {
+ const screen = render();
+ const i = screen.getByRole('img');
+ expect(i).toHaveAttribute('aria-label');
+ expect(i.getAttribute('aria-label')).toMatch(/tick-circle/);
+ }
+ );
+});
diff --git a/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-icon.tsx b/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-icon.tsx
index d5f80dcaf..683b0a85a 100644
--- a/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-icon.tsx
+++ b/apps/explorer/src/app/components/txs/details/proposal/signature-bundle/bundle-icon.tsx
@@ -10,9 +10,6 @@ export interface IconForBundleStatusProps {
* Naively select an icon for an asset. If it is enabled, we show a tick - anything
* else is assumed to be 'in progress'. There should only be a signature bundle or the
* asset should not exist
- *
- * @param param0
- * @returns
*/
export const IconForBundleStatus = ({ status }: IconForBundleStatusProps) => {
const i: IconName = status === 'STATUS_ENABLED' ? 'tick-circle' : 'clean';
diff --git a/apps/explorer/src/app/routes/txs/id/tx-details.spec.tsx b/apps/explorer/src/app/routes/txs/id/tx-details.spec.tsx
index 07dba253a..c7f18b1d8 100644
--- a/apps/explorer/src/app/routes/txs/id/tx-details.spec.tsx
+++ b/apps/explorer/src/app/routes/txs/id/tx-details.spec.tsx
@@ -9,7 +9,7 @@ import type {
// Note: Long enough that there is a truncated output and a full output
const pubKey =
'67755549e43e95f0697f83b2bf419c6ccc18eee32a8a61b8ba6f59471b86fbef';
-const hash = '7416753A30622A9E24A06F0172D6C33A95186B36806D96345C6DC5A23FA3F283';
+const hash = '7416753a30622a9e24a06f0172d6c33a95186b36806d96345c6dc5a23fa3f283';
const height = '52987';
const txData: BlockExplorerTransactionResult = {