diff --git a/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service.spec.ts b/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service.spec.ts index abee1dce23fd..beda94c82f85 100644 --- a/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service.spec.ts +++ b/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service.spec.ts @@ -1002,9 +1002,11 @@ describe('WorkPackageCardDragAndDropService — real two-list directive fixture' standalone: true, imports: [OpSortableListsDirective, TestCardListHostComponent], template: ` -
- - +
+
+ + +
`, }) diff --git a/frontend/src/app/shared/directives/sortable-lists/sortable-lists.directive.behavior.spec.ts b/frontend/src/app/shared/directives/sortable-lists/sortable-lists.directive.behavior.spec.ts index cd78031781fc..4e5dca23b015 100644 --- a/frontend/src/app/shared/directives/sortable-lists/sortable-lists.directive.behavior.spec.ts +++ b/frontend/src/app/shared/directives/sortable-lists/sortable-lists.directive.behavior.spec.ts @@ -41,17 +41,9 @@ import { type SortableListsRemovedEvent, } from './sortable-lists.directive'; -// Deliberately does NOT `vi.mock` Pragmatic's autoscroll module: under the -// repo's `isolate:false` vitest config, spec files share a module registry -// within a worker, and this specifier is also `vi.mock`-ed (independently) -// by `sortable-lists-engine.spec.ts` for its own, unrelated engine-level -// tests. Two spec files independently mocking the same specifier crashes the -// shared worker (an unhandled "error when mocking a module" rejection, -// reproducible regardless of factory shape); the mock is also effectively -// global for the whole worker once registered, so even a "compatible" mock -// here would fight with that file's synthetic (non-real) replacement in -// combined runs. Instead this suite observes the REAL implementation's own -// documented side effects directly: +// Deliberately does NOT `vi.mock` Pragmatic's autoscroll module: this suite +// observes the REAL implementation's own documented side effects, which is +// what makes the scroll-container assertions below meaningful. // - the scroll-fallback tests below assert on `data-auto-scrollable`, the // attribute Pragmatic's real `autoScrollForElements` sets on registration // and removes on cleanup (see `@atlaskit/pragmatic-drag-and-drop-auto-scroll`'s @@ -71,20 +63,22 @@ describe('sortable-lists directive group behavior', () => { @Component({ imports: [OpSortableListsDirective, OpSortableListsItemDirective], template: ` -
- @for (entry of items(); track entry.id) { -
{{ entry.id }}
- } +
+
+ @for (entry of items(); track entry.id) { +
{{ entry.id }}
+ } +
`, }) @@ -225,10 +219,12 @@ describe('sortable-lists directive group behavior', () => { @Component({ imports: [OpSortableListsDirective, OpSortableListsListDirective, OpSortableListsItemDirective], template: ` -
- @for (id of ids(); track id) { -
{{ id }}
- } +
+
+ @for (id of ids(); track id) { +
{{ id }}
+ } +
`, }) @@ -291,29 +287,31 @@ describe('sortable-lists directive group behavior', () => { @Component({ imports: [OpSortableListsDirective, OpSortableListsListDirective, OpSortableListsItemDirective], template: ` -
-
- @for (id of idsA(); track id) { -
{{ id }}
- } -
+
+
+
+ @for (id of idsA(); track id) { +
{{ id }}
+ } +
-
- @for (id of idsB(); track id) { -
{{ id }}
- } +
+ @for (id of idsB(); track id) { +
{{ id }}
+ } +
`, @@ -409,22 +407,24 @@ describe('sortable-lists directive group behavior', () => { @Component({ imports: [OpSortableListsDirective, OpSortableListsListDirective, OpSortableListsItemDirective], template: ` -
- @if (explicit()) { -
+
+
+ @if (explicit()) { +
+ @for (id of ids(); track id) { +
{{ id }}
+ } +
+ } @else { @for (id of ids(); track id) {
{{ id }}
} -
- } @else { - @for (id of ids(); track id) { -
{{ id }}
} - } +
`, }) @@ -495,16 +495,23 @@ describe('sortable-lists directive group behavior', () => { }); describe('nested roots', () => { + // Two independent roots each need their own closest-scrollable-ancestor, + // or both walks terminate on the same element and Pragmatic ends up with + // two registrations on one node — see sortable-lists-engine.ts:142-149. @Component({ imports: [OpSortableListsDirective, OpSortableListsItemDirective], template: ` -
-
outer-a
- -
- @for (id of innerIds(); track id) { -
{{ id }}
- } +
+
+
outer-a
+ +
+
+ @for (id of innerIds(); track id) { +
{{ id }}
+ } +
+
`, @@ -548,27 +555,35 @@ describe('sortable-lists directive group behavior', () => { // list's id — DI's nearest-list lookup does not stop at the inner root, // so without the ownership check it would register against the inner // engine under an id that engine never registered a list for. + // + // The inner wrapper also gives the inner root its own scrollable + // ancestor, distinct from the outer root's, so the two don't collide on + // one Pragmatic registration — see sortable-lists-engine.ts:142-149. @Component({ imports: [OpSortableListsDirective, OpSortableListsListDirective, OpSortableListsItemDirective], template: ` -
-
+
+
- @for (id of innerIds(); track id) { -
{{ id }}
- } +
+
+ @for (id of innerIds(); track id) { +
{{ id }}
+ } +
+
@@ -637,10 +652,12 @@ describe('sortable-lists directive group behavior', () => { @Component({ imports: [OpSortableListsDirective, OpSortableListsListDirective, OpSortableListsItemDirective], template: ` -
- @for (id of ids(); track id) { -
{{ id }}
- } +
+
+ @for (id of ids(); track id) { +
{{ id }}
+ } +
`, }) @@ -778,20 +795,22 @@ describe('sortable-lists directive group behavior', () => { @Component({ imports: [OpSortableListsDirective, OpSortableListsItemDirective], template: ` -
- @for (id of ids(); track id) { -
{{ id }}
- } +
+
+ @for (id of ids(); track id) { +
{{ id }}
+ } +
`, }) @@ -804,10 +823,12 @@ describe('sortable-lists directive group behavior', () => { @Component({ imports: [OpSortableListsDirective, OpSortableListsItemDirective], template: ` -
- @for (id of ids(); track id) { -
{{ id }}
- } +
+
+ @for (id of ids(); track id) { +
{{ id }}
+ } +
`, }) diff --git a/frontend/src/app/shared/helpers/drag-and-drop/drag-and-drop.service.spec.ts b/frontend/src/app/shared/helpers/drag-and-drop/drag-and-drop.service.spec.ts index 90732497014a..381b7e72e4ec 100644 --- a/frontend/src/app/shared/helpers/drag-and-drop/drag-and-drop.service.spec.ts +++ b/frontend/src/app/shared/helpers/drag-and-drop/drag-and-drop.service.spec.ts @@ -60,7 +60,10 @@ function buildRow(id:string, opts:{ handle?:boolean } = {}):HTMLElement { function buildContainer(ids:string[], opts:{ handle?:boolean } = {}):{ container:HTMLElement; rows:HTMLElement[] } { const container = document.createElement('div'); - container.style.cssText = 'width:200px;'; + // `overflow-y` with no height keeps the container a scroll container for + // Pragmatic's computed-overflow check. This also computes overflow-x to + // `auto`, but rows are exactly 200px wide in a 200px container. + container.style.cssText = 'width:200px; overflow-y:auto;'; const rows = ids.map((id) => { const row = buildRow(id, opts); container.appendChild(row); @@ -159,7 +162,7 @@ describe('DragAndDropService', () => { describe('container append', () => { it('reports a drop below the rows as a null-target append intent', async () => { const { container, rows } = buildContainer(['a0', 'a1']); - container.style.cssText = 'width:200px; padding-bottom:40px;'; + container.style.cssText = 'width:200px; padding-bottom:40px; overflow-y:auto;'; const onMoved = vi.fn(); service.register(buildMember(container, { onMoved })); diff --git a/frontend/src/common/drag-and-drop/sortable-lists-engine.preview.spec.ts b/frontend/src/common/drag-and-drop/sortable-lists-engine.preview.spec.ts index ce1b72862165..2654b58a332b 100644 --- a/frontend/src/common/drag-and-drop/sortable-lists-engine.preview.spec.ts +++ b/frontend/src/common/drag-and-drop/sortable-lists-engine.preview.spec.ts @@ -70,6 +70,10 @@ describe('createSortableRoot drag preview offset', () => { function setup():{ rows:HTMLElement[] } { const root = document.createElement('div'); + // The engine attaches auto-scroll to this element directly, with no + // ancestor walk, so the overflow has to sit here. This also computes + // overflow-x to auto; 600px rows just happen to fit the viewport. + root.style.cssText = 'overflow-y:auto;'; const rows = ['a', 'b'].map((id) => { const row = document.createElement('div'); row.style.cssText = 'height:40px; width:600px;';