diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue
index 212027ff3b..1cd3dec8d6 100644
--- a/packages/editor-ui/src/components/RunData.vue
+++ b/packages/editor-ui/src/components/RunData.vue
@@ -70,48 +70,24 @@
data-test-id="ndv-edit-pinned-data"
@click="enterEditMode({ origin: 'editIconButton' })"
/>
-
-
-
- {{ $locale.baseText('ndv.pinData.pin.title') }}
-
- {{ $locale.baseText('ndv.pinData.pin.description') }}
-
- {{ $locale.baseText('ndv.pinData.pin.link') }}
-
-
-
-
-
-
- {{ $locale.baseText('node.discovery.pinData.ndv') }}
-
-
-
-
+
await import('@/components/RunDataTable.vue'),
@@ -649,6 +626,7 @@ export default defineComponent({
RunDataSchema,
RunDataHtml,
RunDataSearch,
+ RunDataPinButton,
},
props: {
node: {
@@ -1727,12 +1705,6 @@ export default defineComponent({
max-width: 240px;
}
-.pinDataButton {
- svg {
- transition: transform 0.3s ease;
- }
-}
-
.spinner {
* {
color: var(--color-primary);
diff --git a/packages/editor-ui/src/components/RunDataPinButton.vue b/packages/editor-ui/src/components/RunDataPinButton.vue
new file mode 100644
index 0000000000..9fd3539515
--- /dev/null
+++ b/packages/editor-ui/src/components/RunDataPinButton.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+ {{ locale.baseText('ndv.pinData.pin.binary') }}
+
+
+ {{ locale.baseText('node.discovery.pinData.ndv') }}
+
+
+ {{ locale.baseText('ndv.pinData.pin.title') }}
+
+ {{ locale.baseText('ndv.pinData.pin.description') }}
+
+
+ {{ locale.baseText('ndv.pinData.pin.link') }}
+
+
+
+
+
+
+
+
+
diff --git a/packages/editor-ui/src/components/__tests__/RunDataPinButton.test.ts b/packages/editor-ui/src/components/__tests__/RunDataPinButton.test.ts
new file mode 100644
index 0000000000..1e6b7958c8
--- /dev/null
+++ b/packages/editor-ui/src/components/__tests__/RunDataPinButton.test.ts
@@ -0,0 +1,124 @@
+import { createTestingPinia } from '@pinia/testing';
+import { cleanup, waitFor } from '@testing-library/vue';
+import userEvent from '@testing-library/user-event';
+import { createComponentRenderer } from '@/__tests__/render';
+import RunDataPinButton from '@/components/RunDataPinButton.vue';
+import { STORES } from '@/constants';
+
+const renderComponent = createComponentRenderer(RunDataPinButton, {
+ global: {
+ stubs: ['font-awesome-icon'],
+ plugins: [
+ createTestingPinia({
+ initialState: {
+ [STORES.SETTINGS]: {
+ settings: {
+ templates: {
+ enabled: true,
+ host: 'https://api.n8n.io/api/',
+ },
+ },
+ },
+ },
+ }),
+ ],
+ },
+ props: {
+ tooltipContentsVisibility: {
+ binaryDataTooltipContent: false,
+ pinDataDiscoveryTooltipContent: false,
+ },
+ dataPinningDocsUrl: '',
+ pinnedData: {
+ hasData: false,
+ },
+ disabled: false,
+ },
+});
+
+describe('RunDataPinButton.vue', () => {
+ beforeEach(cleanup);
+
+ it('shows default tooltip content only on button hover', async () => {
+ const { getByRole, queryByRole, emitted } = renderComponent();
+
+ expect(queryByRole('tooltip')).not.toBeInTheDocument();
+
+ expect(getByRole('button')).toBeEnabled();
+ await userEvent.hover(getByRole('button'));
+
+ expect(getByRole('tooltip')).toBeVisible();
+ expect(getByRole('tooltip')).toHaveTextContent('More info');
+
+ await userEvent.click(getByRole('button'));
+ expect(emitted().togglePinData).toBeDefined();
+ });
+
+ it('shows binary data tooltip content only on disabled button hover', async () => {
+ const { getByRole, queryByRole, emitted } = renderComponent({
+ props: {
+ tooltipContentsVisibility: {
+ binaryDataTooltipContent: true,
+ pinDataDiscoveryTooltipContent: false,
+ },
+ disabled: true,
+ },
+ });
+
+ expect(queryByRole('tooltip')).not.toBeInTheDocument();
+ expect(getByRole('button')).toBeDisabled();
+
+ await userEvent.hover(getByRole('button'));
+
+ expect(getByRole('tooltip')).toBeVisible();
+ expect(getByRole('tooltip')).toHaveTextContent('disabled');
+
+ await userEvent.click(getByRole('button'));
+ expect(emitted().togglePinData).not.toBeDefined();
+ });
+
+ it('shows pin data discoverability tooltip immediately (not on hover)', async () => {
+ const { getByRole } = renderComponent({
+ props: {
+ tooltipContentsVisibility: {
+ binaryDataTooltipContent: false,
+ pinDataDiscoveryTooltipContent: true,
+ },
+ },
+ });
+
+ await waitFor(() => {
+ expect(getByRole('tooltip')).toBeVisible();
+ expect(getByRole('tooltip')).toHaveTextContent('instead of waiting');
+ });
+ expect(getByRole('button')).toBeEnabled();
+
+ await userEvent.hover(getByRole('button'));
+
+ expect(getByRole('tooltip')).toBeVisible();
+ expect(getByRole('tooltip')).toHaveTextContent('instead of waiting');
+ });
+
+ it('shows binary data tooltip content even if discoverability tooltip enabled', async () => {
+ const { getByRole } = renderComponent({
+ props: {
+ tooltipContentsVisibility: {
+ binaryDataTooltipContent: true,
+ pinDataDiscoveryTooltipContent: true,
+ },
+ disabled: true,
+ },
+ });
+
+ await waitFor(() => {
+ expect(getByRole('tooltip')).toBeVisible();
+ expect(getByRole('tooltip')).toHaveTextContent('disabled');
+ });
+ expect(getByRole('button')).toBeDisabled();
+
+ await userEvent.hover(getByRole('button'));
+
+ expect(getByRole('tooltip')).toBeVisible();
+ expect(getByRole('tooltip')).toHaveTextContent('disabled');
+ });
+});
diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json
index 3db1094d1c..b8bf30503a 100644
--- a/packages/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/editor-ui/src/plugins/i18n/locales/en.json
@@ -820,6 +820,7 @@
"ndv.title.renameNode": "Rename node",
"ndv.pinData.pin.title": "Pin data",
"ndv.pinData.pin.description": "Node will always output this data instead of executing.",
+ "ndv.pinData.pin.binary": "Pin Data is disabled as this node's output contains binary data.",
"ndv.pinData.pin.link": "More info",
"ndv.pinData.pin.multipleRuns.title": "Run #{index} was pinned",
"ndv.pinData.pin.multipleRuns.description": "This run will be outputted each time the node is run.",