diff --git a/packages/frontend/editor-ui/src/components/SourceControlPullModal.ee.test.ts b/packages/frontend/editor-ui/src/components/SourceControlPullModal.ee.test.ts
index c24ef96ed3..8aed6910b6 100644
--- a/packages/frontend/editor-ui/src/components/SourceControlPullModal.ee.test.ts
+++ b/packages/frontend/editor-ui/src/components/SourceControlPullModal.ee.test.ts
@@ -6,20 +6,62 @@ import userEvent from '@testing-library/user-event';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { mockedStore } from '@/__tests__/utils';
import { waitFor } from '@testing-library/dom';
+import { reactive } from 'vue';
const eventBus = createEventBus();
+// Mock Vue Router to eliminate injection warnings
+const mockRoute = reactive({
+ params: {},
+ query: {},
+ path: '/',
+ name: 'TestRoute',
+});
+
+vi.mock('vue-router', () => ({
+ useRoute: () => mockRoute,
+ useRouter: () => ({
+ push: vi.fn(),
+ replace: vi.fn(),
+ go: vi.fn(),
+ }),
+ RouterLink: {
+ template: '',
+ props: ['to', 'target'],
+ },
+}));
+
+// Mock the toast composable to prevent Element Plus DOM errors
+vi.mock('@/composables/useToast', () => ({
+ useToast: () => ({
+ showMessage: vi.fn(),
+ showError: vi.fn(),
+ showSuccess: vi.fn(),
+ clear: vi.fn(),
+ }),
+}));
+
const DynamicScrollerStub = {
props: {
items: Array,
+ minItemSize: Number,
+ class: String,
+ style: [String, Object],
},
- template: '
',
+ template:
+ '
',
methods: {
scrollToItem: vi.fn(),
},
};
const DynamicScrollerItemStub = {
+ props: {
+ item: Object,
+ active: Boolean,
+ sizeDependencies: Array,
+ dataIndex: Number,
+ },
template: '',
};
@@ -38,6 +80,13 @@ const renderModal = createComponentRenderer(SourceControlPullModalEe, {
`,
},
+ EnvFeatureFlag: {
+ template: '
',
+ },
+ N8nIconButton: {
+ template: '',
+ props: ['icon', 'type', 'class'],
+ },
},
},
});
@@ -66,8 +115,11 @@ const sampleFiles = [
];
describe('SourceControlPushModal', () => {
+ let sourceControlStore: ReturnType>;
+
beforeEach(() => {
createTestingPinia();
+ sourceControlStore = mockedStore(useSourceControlStore);
});
it('mounts', () => {
@@ -97,7 +149,6 @@ describe('SourceControlPushModal', () => {
});
it('should force pull', async () => {
- const sourceControlStore = mockedStore(useSourceControlStore);
const { getByTestId } = renderModal({
props: {
data: {
@@ -111,4 +162,111 @@ describe('SourceControlPushModal', () => {
await waitFor(() => expect(sourceControlStore.pullWorkfolder).toHaveBeenCalledWith(true));
});
+
+ it('should render diff button with file-diff icon for workflow items', () => {
+ const workflowFile = {
+ ...sampleFiles[0], // workflow file
+ type: 'workflow',
+ };
+
+ const { container } = renderModal({
+ props: {
+ data: {
+ eventBus,
+ status: [workflowFile],
+ },
+ },
+ });
+
+ // Check if a button with file-diff icon would be rendered (via class since icon is a prop)
+ const diffButton = container.querySelector('button');
+ expect(diffButton).toBeInTheDocument();
+ });
+
+ it('should not render diff button for non-workflow items', () => {
+ const credentialFile = {
+ ...sampleFiles[1], // credential file
+ type: 'credential',
+ };
+
+ const { container } = renderModal({
+ props: {
+ data: {
+ eventBus,
+ status: [credentialFile],
+ },
+ },
+ });
+
+ // For credential files, there should be no additional buttons in the item actions
+ const itemActions = container.querySelector('[class*="itemActions"]');
+ const buttons = itemActions?.querySelectorAll('button');
+ expect(buttons).toHaveLength(0);
+ });
+
+ it('should render item names with ellipsis for long text', () => {
+ const longNameFile = {
+ ...sampleFiles[0],
+ name: 'This is a very long workflow name that should be truncated with ellipsis to prevent wrapping to multiple lines',
+ };
+
+ const { container } = renderModal({
+ props: {
+ data: {
+ eventBus,
+ status: [longNameFile],
+ },
+ },
+ });
+
+ // Check if the itemName container exists and has the proper structure
+ const nameContainer = container.querySelector('[class*="itemName"]');
+ expect(nameContainer).toBeInTheDocument();
+
+ // Check if the RouterLink stub is rendered (since the name is rendered inside it)
+ const routerLink = nameContainer?.querySelector('a');
+ expect(routerLink).toBeInTheDocument();
+ });
+
+ it('should render badges and actions in separate container', () => {
+ const { getAllByTestId } = renderModal({
+ props: {
+ data: {
+ eventBus,
+ status: sampleFiles,
+ },
+ },
+ });
+
+ const listItems = getAllByTestId('pull-modal-item');
+
+ // Each list item should have the new structure with itemActions container
+ listItems.forEach((item) => {
+ const actionsContainer = item.querySelector('[class*="itemActions"]');
+ expect(actionsContainer).toBeInTheDocument();
+
+ // Badge should be inside actions container
+ const badge = actionsContainer?.querySelector('[class*="listBadge"]');
+ expect(badge).toBeInTheDocument();
+ });
+ });
+
+ it('should apply proper spacing and alignment styles', () => {
+ const { container, getAllByTestId } = renderModal({
+ props: {
+ data: {
+ eventBus,
+ status: sampleFiles,
+ },
+ },
+ });
+
+ // Check if the scroller container exists (using generic div since stub doesn't preserve CSS modules)
+ const scrollerContainer = container.querySelector('div');
+ expect(scrollerContainer).toBeInTheDocument();
+
+ // Check if list items exist and have proper structure
+ const listItems = getAllByTestId('pull-modal-item');
+ expect(listItems.length).toBeGreaterThan(0);
+ });
});
diff --git a/packages/frontend/editor-ui/src/components/SourceControlPullModal.ee.vue b/packages/frontend/editor-ui/src/components/SourceControlPullModal.ee.vue
index 74271a5389..d6d72259ca 100644
--- a/packages/frontend/editor-ui/src/components/SourceControlPullModal.ee.vue
+++ b/packages/frontend/editor-ui/src/components/SourceControlPullModal.ee.vue
@@ -141,7 +141,7 @@ function openDiffModal(id: string) {
ref="scroller"
:items="files"
:min-item-size="47"
- class="full-height scroller"
+ :class="$style.scroller"
style="max-height: 440px"
>
@@ -160,32 +160,37 @@ function openDiffModal(id: string) {
:data-index="index"
>
-
- {{ item.name }}
-
-
- {{ item.name }}
-
-
{{ item.name }}
-
- {{ getStatusText(item.status) }}
-
-
-
-
+
+
+ {{ item.name }}
+
+
+ {{ item.name }}
+
+ {{ item.name }}
+
+
+
+ {{ getStatusText(item.status) }}
+
+
+
+
+
@@ -207,8 +212,13 @@ function openDiffModal(id: string) {