Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@
{ provide: TabMenuService, useValue: EditorTabMenuService },
{ provide: PermissionsService, useMock: mockedPermissionsService },
{ provide: LynxWorkspaceService, useMock: mockedLynxWorkspaceService },
provideNoopAnimations()

Check warning on line 188 in src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.spec.ts

View workflow job for this annotation

GitHub Actions / Lint and Prettier (22.13.0, 11.11.0, 11.10.0)

`provideNoopAnimations` is deprecated. 20.2 Use `animate.enter` or `animate.leave` instead. Intent to remove in v23
]
}));

Expand Down Expand Up @@ -3249,6 +3249,196 @@
}));
});

describe('source scroll sync', () => {
/**
* Sets up the source scroll container and the source/target selection bounds that syncScroll reads, and returns
* a getter for the source scroll position it produces. Selection bounds are relative to the viewport, as Quill
* reports them.
*/
function setUpScrollSync(
env: TestEnvironment,
options: {
scrollTop: number;
containerTop: number;
containerHeight: number;
sourceSelectionTop: number;
sourceSelectionHeight: number;
targetSelectionTop: number;
}
): { scrollTop: () => number } {
env.clickSegmentRef('verse_1_1');
env.component.targetFocused = true;
expect(env.component.source?.segment).withContext('source segment').not.toBeNull();

const container = env.component['sourceScrollContainer'] as HTMLElement;
let scrollTop: number = options.scrollTop;
Object.defineProperty(container, 'scrollTop', {
get: () => scrollTop,
set: (value: number) => (scrollTop = value),
configurable: true
});
Object.defineProperty(container, 'clientHeight', { value: options.containerHeight, configurable: true });
spyOn(container, 'getBoundingClientRect').and.returnValue({
top: options.containerTop,
height: options.containerHeight,
bottom: options.containerTop + options.containerHeight
} as DOMRect);
spyOn(env.component.source!.editor!.selection, 'getBounds').and.returnValue({
top: options.sourceSelectionTop,
height: options.sourceSelectionHeight,
bottom: options.sourceSelectionTop + options.sourceSelectionHeight,
left: 0,
right: 0,
width: 0
});
spyOn(env.component.target!.editor!.selection, 'getBounds').and.returnValue({
top: options.targetSelectionTop,
height: 20,
bottom: options.targetSelectionTop + 20,
left: 0,
right: 0,
width: 0
});

return { scrollTop: () => scrollTop };
}

it('scrolls the source selection to the target selection when scrolling down', fakeAsync(() => {
const env = new TestEnvironment();
env.setProjectUserConfig();
env.wait();

// Source selection is far below the visible area of the source
const scroll = setUpScrollSync(env, {
scrollTop: 0,
containerTop: 100,
containerHeight: 600,
sourceSelectionTop: 1300,
sourceSelectionHeight: 140,
targetSelectionTop: 400
});

env.component['syncScroll']();

// The source selection lands where the target selection is, which is within view
expect(scroll.scrollTop()).toEqual(900);

env.dispose();
}));

it('scrolls the source selection to the target selection when scrolling up', fakeAsync(() => {
const env = new TestEnvironment();
env.setProjectUserConfig();
env.wait();

// Source selection is above the visible area of the source, as it is when selecting an earlier verse
const scroll = setUpScrollSync(env, {
scrollTop: 1900,
containerTop: 100,
containerHeight: 600,
sourceSelectionTop: -600,
sourceSelectionHeight: 140,
targetSelectionTop: 400
});

env.component['syncScroll']();

expect(scroll.scrollTop()).toEqual(900);

env.dispose();
}));

it('scrolls further when the target selection is above the top of the source', fakeAsync(() => {
const env = new TestEnvironment();
env.setProjectUserConfig();
env.wait();

// The target selection is 50 pixels above the top of the source container
const scroll = setUpScrollSync(env, {
scrollTop: 0,
containerTop: 100,
containerHeight: 600,
sourceSelectionTop: 1300,
sourceSelectionHeight: 140,
targetSelectionTop: 50
});

env.component['syncScroll']();

// Aligning the selections would put the source selection 50 pixels above the container, so scroll 50 less
expect(scroll.scrollTop()).toEqual(1200);

env.dispose();
}));

it('scrolls further when the bottom of the source selection is below the source', fakeAsync(() => {
const env = new TestEnvironment();
env.setProjectUserConfig();
env.wait();

// Aligned with the target selection, the source selection would extend 100 pixels below the container
const scroll = setUpScrollSync(env, {
scrollTop: 0,
containerTop: 100,
containerHeight: 600,
sourceSelectionTop: 1300,
sourceSelectionHeight: 200,
targetSelectionTop: 600
});

env.component['syncScroll']();

expect(scroll.scrollTop()).toEqual(800);

env.dispose();
}));

it('scrolls a source selection exactly as tall as the source to the top of the source', fakeAsync(() => {
const env = new TestEnvironment();
env.setProjectUserConfig();
env.wait();

// Aligned with the target selection, the source selection would extend 300 pixels below the container, and
// scrolling that far up puts its top exactly at the top of the container
const scroll = setUpScrollSync(env, {
scrollTop: 0,
containerTop: 100,
containerHeight: 600,
sourceSelectionTop: 1300,
sourceSelectionHeight: 600,
targetSelectionTop: 400
});

env.component['syncScroll']();

expect(scroll.scrollTop()).toEqual(1200);

env.dispose();
}));

it('does not scroll a source selection taller than the source past its top', fakeAsync(() => {
const env = new TestEnvironment();
env.setProjectUserConfig();
env.wait();

// The source selection is taller than the container, so showing its bottom would hide its top
const scroll = setUpScrollSync(env, {
scrollTop: 0,
containerTop: 100,
containerHeight: 600,
sourceSelectionTop: 1300,
sourceSelectionHeight: 800,
targetSelectionTop: 400
});

env.component['syncScroll']();

expect(scroll.scrollTop()).toEqual(900);

env.dispose();
}));
});

describe('Translator settings enabled/disabled', () => {
it('shows translator settings when the user has a paratext role', fakeAsync(() => {
const navigationParams: Params = { projectId: 'project01', bookId: 'MRK' };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2275,19 +2275,22 @@ export class EditorComponent extends DataLoadingComponent implements OnDestroy,
let newScrollTop: number =
this.sourceScrollContainer.scrollTop + sourceSelectionBounds.top - targetSelectionBounds.top;

// Check to see if the top of source selection would be visible after the scroll adjustment
// Check whether the top of the source selection would be visible after the scroll. The scroll above puts the
// source selection where the target selection is, so the target selection's position decides visibility, not
// where the source selection is now.
const sourceTopPosition: number =
sourceSelectionBounds.top - this.sourceScrollContainer.getBoundingClientRect().top;
targetSelectionBounds.top - this.sourceScrollContainer.getBoundingClientRect().top;

// Check to see if the bottom of source selection would be visible after the scroll adjustment
// Check whether the bottom of the source selection would be visible after the scroll
const sourceBottomPosition: number =
sourceTopPosition + sourceSelectionBounds.height - this.sourceScrollContainer.clientHeight;

// Adjust the scroll to ensure the selection fits within the container
// Only adjust the bottom position so long as that doesn't hide the top position i.e. a long verse(s)
// Adjust the scroll so the selection fits in the container. Scroll for the bottom only when that keeps the top
// visible. A selection taller than the container keeps its top in view instead. A selection exactly as tall as
// the container fits, with its top at the top of the container.
if (sourceTopPosition < 0) {
newScrollTop += sourceTopPosition;
} else if (sourceBottomPosition > 0 && sourceTopPosition - sourceBottomPosition > 0) {
} else if (sourceBottomPosition > 0 && sourceTopPosition - sourceBottomPosition >= 0) {
newScrollTop += sourceBottomPosition;
}

Expand Down
Loading